@libsql/client
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -5,4 +5,4 @@ "use strict"; | ||
const api_js_1 = require("./api.js"); | ||
const help_js_1 = require("./help.js"); | ||
const uri_js_1 = require("./uri.js"); | ||
const util_js_1 = require("./util.js"); | ||
function expandConfig(config, preferHttp) { | ||
@@ -60,3 +60,3 @@ if (typeof config !== "object") { | ||
`got ${JSON.stringify(uri.scheme + ":")}. ` + | ||
`For more information, please read ${help_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
`For more information, please read ${util_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
} | ||
@@ -63,0 +63,0 @@ if (uri.fragment !== undefined) { |
@@ -29,3 +29,5 @@ "use strict"; | ||
const api_js_1 = require("./api.js"); | ||
const util_js_1 = require("./util.js"); | ||
class HranaTransaction { | ||
#mode; | ||
// Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the | ||
@@ -35,3 +37,4 @@ // BEGIN statement yet. | ||
/** @private */ | ||
constructor() { | ||
constructor(mode) { | ||
this.#mode = mode; | ||
this.#started = undefined; | ||
@@ -53,3 +56,3 @@ } | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run("BEGIN"); | ||
const beginPromise = beginStep.run((0, util_js_1.transactionModeToBegin)(this.#mode)); | ||
// Execute the `stmt` only if the BEGIN succeeded, to make sure that we don't execute it | ||
@@ -89,2 +92,93 @@ // outside of a transaction. | ||
} | ||
async batch(stmts) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new api_js_1.LibsqlError("Cannot execute a batch because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
const hranaStmts = stmts.map(stmtToHrana); | ||
// This is analogous to `execute()`, please read the comments there | ||
let rowsPromises; | ||
if (this.#started === undefined) { | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run((0, util_js_1.transactionModeToBegin)(this.#mode)); | ||
let lastStep = beginStep; | ||
rowsPromises = hranaStmts.map((hranaStmt) => { | ||
const stmtStep = batch.step() | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
this.#started = batch.execute() | ||
.then(() => beginPromise) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
await this.#started; | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
let lastStep = undefined; | ||
rowsPromises = hranaStmts.map((hranaStmt) => { | ||
const stmtStep = batch.step(); | ||
if (lastStep !== undefined) { | ||
stmtStep.condition(hrana.BatchCond.ok(lastStep)); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
await batch.execute(); | ||
} | ||
const resultSets = []; | ||
for (const rowsPromise of rowsPromises) { | ||
const rows = await rowsPromise; | ||
if (rows === undefined) { | ||
throw new api_js_1.LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
} | ||
resultSets.push(resultSetFromHrana(rows)); | ||
} | ||
return resultSets; | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
} | ||
async executeMultiple(sql) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new api_js_1.LibsqlError("Cannot execute statements because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
if (this.#started === undefined) { | ||
// If the transaction hasn't started yet, start it now | ||
this.#started = stream.run((0, util_js_1.transactionModeToBegin)(this.#mode)) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
// Wait until the transaction has started | ||
await this.#started; | ||
} | ||
await stream.sequence(sql); | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
} | ||
async rollback() { | ||
@@ -149,5 +243,5 @@ try { | ||
exports.HranaTransaction = HranaTransaction; | ||
async function executeHranaBatch(batch, hranaStmts) { | ||
async function executeHranaBatch(mode, batch, hranaStmts) { | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run("BEGIN"); | ||
const beginPromise = beginStep.run((0, util_js_1.transactionModeToBegin)(mode)); | ||
let lastStep = beginStep; | ||
@@ -154,0 +248,0 @@ const stmtPromises = hranaStmts.map((hranaStmt) => { |
@@ -33,6 +33,6 @@ "use strict"; | ||
const config_js_1 = require("./config.js"); | ||
const help_js_1 = require("./help.js"); | ||
const hrana_js_1 = require("./hrana.js"); | ||
const sql_cache_js_1 = require("./sql_cache.js"); | ||
const uri_js_1 = require("./uri.js"); | ||
const util_js_1 = require("./util.js"); | ||
__exportStar(require("./api.js"), exports); | ||
@@ -47,3 +47,3 @@ function createClient(config) { | ||
throw new api_js_1.LibsqlError('The HTTP client supports only "libsql:", "https:" and "http:" URLs, ' + | ||
`got ${JSON.stringify(config.scheme)}. For more information, please read ${help_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
`got ${JSON.stringify(config.scheme)}. For more information, please read ${util_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
} | ||
@@ -86,3 +86,4 @@ if (config.scheme === "http" && config.tls) { | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = (0, util_js_1.extractBatchArgs)(arg1, arg2); | ||
try { | ||
@@ -100,3 +101,3 @@ const hranaStmts = stmts.map(hrana_js_1.stmtToHrana); | ||
const batch = stream.batch(); | ||
resultsPromise = (0, hrana_js_1.executeHranaBatch)(batch, hranaStmts); | ||
resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, batch, hranaStmts); | ||
} | ||
@@ -112,5 +113,5 @@ finally { | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
try { | ||
return new HttpTransaction(this.#client.openStream()); | ||
return new HttpTransaction(this.#client.openStream(), mode); | ||
} | ||
@@ -121,2 +122,20 @@ catch (e) { | ||
} | ||
async executeMultiple(sql) { | ||
try { | ||
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and | ||
// close the stream in a single HTTP request. | ||
let promise; | ||
const stream = this.#client.openStream(); | ||
try { | ||
promise = stream.sequence(sql); | ||
} | ||
finally { | ||
stream.close(); | ||
} | ||
await promise; | ||
} | ||
catch (e) { | ||
throw (0, hrana_js_1.mapHranaError)(e); | ||
} | ||
} | ||
close() { | ||
@@ -134,4 +153,4 @@ this.#client.close(); | ||
/** @private */ | ||
constructor(stream) { | ||
super(); | ||
constructor(stream, mode) { | ||
super(mode); | ||
this.#stream = stream; | ||
@@ -138,0 +157,0 @@ this.#sqlCache = new sql_cache_js_1.SqlCache(stream, sqlCacheCapacity); |
@@ -25,3 +25,3 @@ "use strict"; | ||
const config_js_1 = require("./config.js"); | ||
const help_js_1 = require("./help.js"); | ||
const util_js_1 = require("./util.js"); | ||
__exportStar(require("./api.js"), exports); | ||
@@ -36,3 +36,3 @@ function createClient(config) { | ||
throw new api_js_1.LibsqlError(`URL scheme ${JSON.stringify(config.scheme + ":")} is not supported by the local sqlite3 client. ` + | ||
`For more information, please read ${help_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
`For more information, please read ${util_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
} | ||
@@ -46,3 +46,3 @@ const authority = config.authority; | ||
'or with three slashes ("file:///absolute/path.db"). ' + | ||
`For more information, please read ${help_js_1.supportedUrlLink}`, "URL_INVALID"); | ||
`For more information, please read ${util_js_1.supportedUrlLink}`, "URL_INVALID"); | ||
} | ||
@@ -88,13 +88,10 @@ if (authority.port !== undefined) { | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = (0, util_js_1.extractBatchArgs)(arg1, arg2); | ||
this.#checkNotClosed(); | ||
const db = new better_sqlite3_1.default(this.path, this.options); | ||
try { | ||
if (stmts.length > 1) { | ||
executeStmt(db, "BEGIN"); | ||
} | ||
executeStmt(db, (0, util_js_1.transactionModeToBegin)(mode)); | ||
const resultSets = stmts.map(stmt => executeStmt(db, stmt)); | ||
if (stmts.length > 1) { | ||
executeStmt(db, "COMMIT"); | ||
} | ||
executeStmt(db, "COMMIT"); | ||
return resultSets; | ||
@@ -106,7 +103,7 @@ } | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
this.#checkNotClosed(); | ||
const db = new better_sqlite3_1.default(this.path, this.options); | ||
try { | ||
executeStmt(db, "BEGIN"); | ||
executeStmt(db, (0, util_js_1.transactionModeToBegin)(mode)); | ||
return new Sqlite3Transaction(db); | ||
@@ -119,2 +116,12 @@ } | ||
} | ||
async executeMultiple(sql) { | ||
this.#checkNotClosed(); | ||
const db = new better_sqlite3_1.default(this.path, this.options); | ||
try { | ||
return executeMultiple(db, sql); | ||
} | ||
finally { | ||
db.close(); | ||
} | ||
} | ||
close() { | ||
@@ -140,2 +147,10 @@ this.closed = true; | ||
} | ||
async batch(stmts) { | ||
this.#checkNotClosed(); | ||
return stmts.map(stmt => executeStmt(this.database, stmt)); | ||
} | ||
async executeMultiple(sql) { | ||
this.#checkNotClosed(); | ||
return executeMultiple(this.database, sql); | ||
} | ||
async rollback() { | ||
@@ -213,6 +228,3 @@ if (!this.database.open) { | ||
catch (e) { | ||
if (e instanceof better_sqlite3_1.default.SqliteError) { | ||
throw new api_js_1.LibsqlError(e.message, e.code, e); | ||
} | ||
throw e; | ||
throw mapSqliteError(e); | ||
} | ||
@@ -271,1 +283,15 @@ } | ||
const maxInteger = 9223372036854775807n; | ||
function executeMultiple(db, sql) { | ||
try { | ||
db.exec(sql); | ||
} | ||
catch (e) { | ||
throw mapSqliteError(e); | ||
} | ||
} | ||
function mapSqliteError(e) { | ||
if (e instanceof better_sqlite3_1.default.SqliteError) { | ||
return new api_js_1.LibsqlError(e.message, e.code, e); | ||
} | ||
return e; | ||
} |
@@ -20,3 +20,3 @@ "use strict"; | ||
const config_js_1 = require("./config.js"); | ||
const help_js_1 = require("./help.js"); | ||
const util_js_1 = require("./util.js"); | ||
const ws_js_1 = require("./ws.js"); | ||
@@ -39,5 +39,5 @@ const http_js_1 = require("./http.js"); | ||
throw new api_js_1.LibsqlError('The client that uses Web standard APIs supports only "libsql:", "wss:", "ws:", "https:" and "http:" URLs, ' + | ||
`got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${help_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
`got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${util_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
} | ||
} | ||
exports._createClient = _createClient; |
@@ -33,6 +33,6 @@ "use strict"; | ||
const config_js_1 = require("./config.js"); | ||
const help_js_1 = require("./help.js"); | ||
const hrana_js_1 = require("./hrana.js"); | ||
const sql_cache_js_1 = require("./sql_cache.js"); | ||
const uri_js_1 = require("./uri.js"); | ||
const util_js_1 = require("./util.js"); | ||
__exportStar(require("./api.js"), exports); | ||
@@ -47,3 +47,3 @@ function createClient(config) { | ||
throw new api_js_1.LibsqlError('The WebSocket client supports only "libsql:", "wss:" and "ws:" URLs, ' + | ||
`got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${help_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
`got ${JSON.stringify(config.scheme + ":")}. For more information, please read ${util_js_1.supportedUrlLink}`, "URL_SCHEME_NOT_SUPPORTED"); | ||
} | ||
@@ -67,3 +67,3 @@ if (config.scheme === "ws" && config.tls) { | ||
`a "${suggestedScheme}:" URL (${JSON.stringify(suggestedUrl)}). ` + | ||
`For more information, please read ${help_js_1.supportedUrlLink}`, "WEBSOCKETS_NOT_SUPPORTED"); | ||
`For more information, please read ${util_js_1.supportedUrlLink}`, "WEBSOCKETS_NOT_SUPPORTED"); | ||
} | ||
@@ -112,3 +112,4 @@ throw (0, hrana_js_1.mapHranaError)(e); | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = (0, util_js_1.extractBatchArgs)(arg1, arg2); | ||
const streamState = await this.#openStream(); | ||
@@ -121,3 +122,3 @@ try { | ||
const batch = streamState.stream.batch(); | ||
const resultsPromise = (0, hrana_js_1.executeHranaBatch)(batch, hranaStmts); | ||
const resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, batch, hranaStmts); | ||
streamState.stream.close(); | ||
@@ -133,3 +134,3 @@ return await resultsPromise; | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
const streamState = await this.#openStream(); | ||
@@ -139,3 +140,3 @@ try { | ||
// network roundtrip | ||
return new WsTransaction(this, streamState); | ||
return new WsTransaction(this, streamState, mode); | ||
} | ||
@@ -147,2 +148,18 @@ catch (e) { | ||
} | ||
async executeMultiple(sql) { | ||
const streamState = await this.#openStream(); | ||
try { | ||
// Schedule all operations synchronously, so they will be pipelined and executed in a single | ||
// network roundtrip. | ||
const promise = streamState.stream.sequence(sql); | ||
streamState.stream.close(); | ||
await promise; | ||
} | ||
catch (e) { | ||
throw (0, hrana_js_1.mapHranaError)(e); | ||
} | ||
finally { | ||
this._closeStream(streamState); | ||
} | ||
} | ||
async #openStream() { | ||
@@ -257,4 +274,4 @@ if (this.closed) { | ||
/** @private */ | ||
constructor(client, state) { | ||
super(); | ||
constructor(client, state, mode) { | ||
super(mode); | ||
this.#client = client; | ||
@@ -261,0 +278,0 @@ this.#streamState = state; |
@@ -53,2 +53,5 @@ /** Configuration object for {@link createClient}. */ | ||
* | ||
* The `mode` parameter selects the transaction mode for the batch; please see {@link TransactionMode} for | ||
* details. | ||
* | ||
* If any of the statements in the batch fails with an error, the batch is aborted, the transaction is | ||
@@ -61,3 +64,3 @@ * rolled back and the returned promise is rejected. | ||
* ```javascript | ||
* const rss = await client.batch([ | ||
* const rss = await client.batch("write", [ | ||
* // batch statement without arguments | ||
@@ -80,4 +83,12 @@ * "DELETE FROM books WHERE name LIKE '%Crusoe'", | ||
*/ | ||
batch(mode: TransactionMode, stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
/** Execute a batch of SQL statement in the `"write"` transaction mode. | ||
* | ||
* Please see {@link batch} for details. | ||
* | ||
* @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next | ||
* major release. | ||
*/ | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
/** Starts an interactive transaction. | ||
/** Start an interactive transaction. | ||
* | ||
@@ -88,2 +99,5 @@ * Interactive transactions allow you to interleave execution of SQL statements with your application | ||
* | ||
* The `mode` parameter selects the transaction mode for the interactive transaction; please see {@link | ||
* TransactionMode} for details. | ||
* | ||
* You **must** make sure that the returned {@link Transaction} object is closed, by calling {@link | ||
@@ -97,3 +111,10 @@ * Transaction.close}, {@link Transaction.commit} or {@link Transaction.rollback}. The best practice is | ||
* // do some operations with the transaction here | ||
* ... | ||
* await transaction.execute({ | ||
* sql: "INSERT INTO books (name, author) VALUES (?, ?)", | ||
* args: ["First Impressions", "Jane Austen"], | ||
* }); | ||
* await transaction.execute({ | ||
* sql: "UPDATE books SET name = ? WHERE name = ?", | ||
* args: ["Pride and Prejudice", "First Impressions"], | ||
* }); | ||
* | ||
@@ -108,3 +129,32 @@ * // if all went well, commit the transaction | ||
*/ | ||
transaction(mode: TransactionMode): Promise<Transaction>; | ||
/** Start an interactive transaction in `"write"` mode. | ||
* | ||
* Please see {@link transaction} for details. | ||
* | ||
* @deprecated Please specify the `mode` explicitly. The default `"write"` will be removed in the next | ||
* major release. | ||
*/ | ||
transaction(): Promise<Transaction>; | ||
/** Execute a sequence of SQL statements separated by semicolons. | ||
* | ||
* The statements are executed sequentially on a new logical database connection. If a statement fails, | ||
* further statements are not executed and this method throws an error. All results from the statements | ||
* are ignored. | ||
* | ||
* We do not wrap the statements in a transaction, but the SQL can contain explicit transaction-control | ||
* statements such as `BEGIN` and `COMMIT`. | ||
* | ||
* This method is intended to be used with existing SQL scripts, such as migrations or small database | ||
* dumps. If you want to execute a sequence of statements programmatically, please use {@link batch} | ||
* instead. | ||
* | ||
* ```javascript | ||
* await client.executeMultiple(` | ||
* CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT NOT NULL, author_id INTEGER NOT NULL); | ||
* CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT NOT NULL); | ||
* `); | ||
* ``` | ||
*/ | ||
executeMultiple(sql: string): Promise<void>; | ||
/** Close the client and release resources. | ||
@@ -138,3 +188,10 @@ * | ||
* // do some operations with the transaction here | ||
* ... | ||
* await transaction.execute({ | ||
* sql: "INSERT INTO books (name, author) VALUES (?, ?)", | ||
* args: ["First Impressions", "Jane Austen"], | ||
* }); | ||
* await transaction.execute({ | ||
* sql: "UPDATE books SET name = ? WHERE name = ?", | ||
* args: ["Pride and Prejudice", "First Impressions"], | ||
* }); | ||
* | ||
@@ -150,10 +207,33 @@ * // if all went well, commit the transaction | ||
export interface Transaction { | ||
/** Executes an SQL statement in the transaction. | ||
/** Execute an SQL statement in this transaction. | ||
* | ||
* If the statement makes any changes to the database, these changes won't be visible to statements | ||
* outside of this transaction until you call {@link rollback}. | ||
* | ||
* ```javascript | ||
* await transaction.execute({ | ||
* sql: "INSERT INTO books (name, author) VALUES (?, ?)", | ||
* args: ["First Impressions", "Jane Austen"], | ||
* }); | ||
* ``` | ||
*/ | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
/** Rolls back any changes from this transaction. | ||
/** Execute a batch of SQL statements in this transaction. | ||
* | ||
* If any of the statements in the batch fails with an error, further statements are not executed and the | ||
* returned promise is rejected with an error, but the transaction is not rolled back. | ||
*/ | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
/** Execute a sequence of SQL statements separated by semicolons. | ||
* | ||
* The statements are executed sequentially in the transaction. If a statement fails, further statements | ||
* are not executed and this method throws an error, but the transaction won't be rolled back. All results | ||
* from the statements are ignored. | ||
* | ||
* This method is intended to be used with existing SQL scripts, such as migrations or small database | ||
* dumps. If you want to execute statements programmatically, please use {@link batch} instead. | ||
*/ | ||
executeMultiple(sql: string): Promise<void>; | ||
/** Roll back any changes from this transaction. | ||
* | ||
* This method closes the transaction and undoes any changes done by the previous SQL statements on this | ||
@@ -163,3 +243,3 @@ * transaction. You cannot call this method after calling {@link commit}, though. | ||
rollback(): Promise<void>; | ||
/** Commits changes from this transaction to the database. | ||
/** Commit changes from this transaction to the database. | ||
* | ||
@@ -171,3 +251,3 @@ * This method closes the transaction and applies all changes done by the previous SQL statement on this | ||
commit(): Promise<void>; | ||
/** Closes the transaction. | ||
/** Close the transaction. | ||
* | ||
@@ -189,2 +269,29 @@ * This method closes the transaction and releases any resources associated with the transaction. If the | ||
} | ||
/** Transaction mode. | ||
* | ||
* The client supports multiple modes for transactions: | ||
* | ||
* - `"write"` is a read-write transaction, started with `BEGIN IMMEDIATE`. This transaction mode supports | ||
* both read statements (`SELECT`) and write statements (`INSERT`, `UPDATE`, `CREATE TABLE`, etc). The libSQL | ||
* server cannot process multiple write transactions concurrently, so if there is another write transaction | ||
* already started, our transaction will wait in a queue before it can begin. | ||
* | ||
* - `"read"` is a read-only transaction, started with `BEGIN TRANSACTION READONLY` (a libSQL extension). This | ||
* transaction mode supports only reads (`SELECT`) and will not accept write statements. The libSQL server can | ||
* handle multiple read transactions at the same time, so we don't need to wait for other transactions to | ||
* complete. A read-only transaction can also be executed on a local replica, so it provides lower latency. | ||
* | ||
* - `"deferred"` is a transaction started with `BEGIN DEFERRED`, which starts as a read transaction, but the | ||
* first write statement will try to upgrade it to a write transaction. However, this upgrade may fail if | ||
* there already is a write transaction executing on the server, so you should be ready to handle these | ||
* failures. | ||
* | ||
* If your transaction includes only read statements, `"read"` is always preferred over `"deferred"` or | ||
* `"write"`, because `"read"` transactions can be executed on a replica and don't block other transactions. | ||
* | ||
* If your transaction includes both read and write statements, you should be using the `"write"` mode most of | ||
* the time. Use the `"deferred"` mode only if you prefer to fail the write transaction instead of waiting for | ||
* the previous write transactions to complete. | ||
*/ | ||
export type TransactionMode = "write" | "read" | "deferred"; | ||
/** Result of executing an SQL statement. | ||
@@ -191,0 +298,0 @@ * |
import { LibsqlError } from "./api.js"; | ||
import { supportedUrlLink } from "./help.js"; | ||
import { parseUri } from "./uri.js"; | ||
import { supportedUrlLink } from "./util.js"; | ||
export function expandConfig(config, preferHttp) { | ||
@@ -5,0 +5,0 @@ if (typeof config !== "object") { |
import * as hrana from "@libsql/hrana-client"; | ||
import type { InStatement, ResultSet, Transaction } from "./api.js"; | ||
import type { InStatement, ResultSet, Transaction, TransactionMode } from "./api.js"; | ||
import type { SqlCache } from "./sql_cache.js"; | ||
@@ -7,3 +7,3 @@ export declare abstract class HranaTransaction implements Transaction { | ||
/** @private */ | ||
constructor(); | ||
constructor(mode: TransactionMode); | ||
/** @private */ | ||
@@ -16,8 +16,10 @@ abstract _getStream(): hrana.Stream; | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
executeMultiple(sql: string): Promise<void>; | ||
rollback(): Promise<void>; | ||
commit(): Promise<void>; | ||
} | ||
export declare function executeHranaBatch(batch: hrana.Batch, hranaStmts: Array<hrana.Stmt>): Promise<Array<ResultSet>>; | ||
export declare function executeHranaBatch(mode: TransactionMode, batch: hrana.Batch, hranaStmts: Array<hrana.Stmt>): Promise<Array<ResultSet>>; | ||
export declare function stmtToHrana(stmt: InStatement): hrana.Stmt; | ||
export declare function resultSetFromHrana(hranaRows: hrana.RowsResult): ResultSet; | ||
export declare function mapHranaError(e: unknown): unknown; |
import * as hrana from "@libsql/hrana-client"; | ||
import { LibsqlError } from "./api.js"; | ||
import { transactionModeToBegin } from "./util.js"; | ||
export class HranaTransaction { | ||
#mode; | ||
// Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the | ||
@@ -8,3 +10,4 @@ // BEGIN statement yet. | ||
/** @private */ | ||
constructor() { | ||
constructor(mode) { | ||
this.#mode = mode; | ||
this.#started = undefined; | ||
@@ -26,3 +29,3 @@ } | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run("BEGIN"); | ||
const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); | ||
// Execute the `stmt` only if the BEGIN succeeded, to make sure that we don't execute it | ||
@@ -62,2 +65,93 @@ // outside of a transaction. | ||
} | ||
async batch(stmts) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new LibsqlError("Cannot execute a batch because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
const hranaStmts = stmts.map(stmtToHrana); | ||
// This is analogous to `execute()`, please read the comments there | ||
let rowsPromises; | ||
if (this.#started === undefined) { | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); | ||
let lastStep = beginStep; | ||
rowsPromises = hranaStmts.map((hranaStmt) => { | ||
const stmtStep = batch.step() | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
this.#started = batch.execute() | ||
.then(() => beginPromise) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
await this.#started; | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
let lastStep = undefined; | ||
rowsPromises = hranaStmts.map((hranaStmt) => { | ||
const stmtStep = batch.step(); | ||
if (lastStep !== undefined) { | ||
stmtStep.condition(hrana.BatchCond.ok(lastStep)); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
await batch.execute(); | ||
} | ||
const resultSets = []; | ||
for (const rowsPromise of rowsPromises) { | ||
const rows = await rowsPromise; | ||
if (rows === undefined) { | ||
throw new LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
} | ||
resultSets.push(resultSetFromHrana(rows)); | ||
} | ||
return resultSets; | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
} | ||
async executeMultiple(sql) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new LibsqlError("Cannot execute statements because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
if (this.#started === undefined) { | ||
// If the transaction hasn't started yet, start it now | ||
this.#started = stream.run(transactionModeToBegin(this.#mode)) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
// Wait until the transaction has started | ||
await this.#started; | ||
} | ||
await stream.sequence(sql); | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
} | ||
async rollback() { | ||
@@ -121,5 +215,5 @@ try { | ||
} | ||
export async function executeHranaBatch(batch, hranaStmts) { | ||
export async function executeHranaBatch(mode, batch, hranaStmts) { | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run("BEGIN"); | ||
const beginPromise = beginStep.run(transactionModeToBegin(mode)); | ||
let lastStep = beginStep; | ||
@@ -126,0 +220,0 @@ const stmtPromises = hranaStmts.map((hranaStmt) => { |
@@ -5,2 +5,3 @@ /// <reference types="node" /> | ||
import type { InStatement, ResultSet, Transaction } from "./api.js"; | ||
import { TransactionMode } from "./api.js"; | ||
import type { ExpandedConfig } from "./config.js"; | ||
@@ -18,4 +19,6 @@ import { HranaTransaction } from "./hrana.js"; | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
batch(mode: TransactionMode, stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
transaction(): Promise<HttpTransaction>; | ||
transaction(mode?: TransactionMode): Promise<HttpTransaction>; | ||
executeMultiple(sql: string): Promise<void>; | ||
close(): void; | ||
@@ -27,3 +30,3 @@ get closed(): boolean; | ||
/** @private */ | ||
constructor(stream: hrana.HttpStream); | ||
constructor(stream: hrana.HttpStream, mode: TransactionMode); | ||
/** @private */ | ||
@@ -30,0 +33,0 @@ _getStream(): hrana.Stream; |
import * as hrana from "@libsql/hrana-client"; | ||
import { LibsqlError } from "./api.js"; | ||
import { expandConfig } from "./config.js"; | ||
import { supportedUrlLink } from "./help.js"; | ||
import { HranaTransaction, executeHranaBatch, stmtToHrana, resultSetFromHrana, mapHranaError, } from "./hrana.js"; | ||
import { SqlCache } from "./sql_cache.js"; | ||
import { encodeBaseUrl } from "./uri.js"; | ||
import { supportedUrlLink, extractBatchArgs } from "./util.js"; | ||
export * from "./api.js"; | ||
@@ -53,3 +53,4 @@ export function createClient(config) { | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = extractBatchArgs(arg1, arg2); | ||
try { | ||
@@ -67,3 +68,3 @@ const hranaStmts = stmts.map(stmtToHrana); | ||
const batch = stream.batch(); | ||
resultsPromise = executeHranaBatch(batch, hranaStmts); | ||
resultsPromise = executeHranaBatch(mode, batch, hranaStmts); | ||
} | ||
@@ -79,5 +80,5 @@ finally { | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
try { | ||
return new HttpTransaction(this.#client.openStream()); | ||
return new HttpTransaction(this.#client.openStream(), mode); | ||
} | ||
@@ -88,2 +89,20 @@ catch (e) { | ||
} | ||
async executeMultiple(sql) { | ||
try { | ||
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the sequence and | ||
// close the stream in a single HTTP request. | ||
let promise; | ||
const stream = this.#client.openStream(); | ||
try { | ||
promise = stream.sequence(sql); | ||
} | ||
finally { | ||
stream.close(); | ||
} | ||
await promise; | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
} | ||
close() { | ||
@@ -100,4 +119,4 @@ this.#client.close(); | ||
/** @private */ | ||
constructor(stream) { | ||
super(); | ||
constructor(stream, mode) { | ||
super(mode); | ||
this.#stream = stream; | ||
@@ -104,0 +123,0 @@ this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); |
import Database from "better-sqlite3"; | ||
import type { Config, Client, Transaction, ResultSet, InStatement } from "./api.js"; | ||
import type { Config, Client, Transaction, TransactionMode, ResultSet, InStatement } from "./api.js"; | ||
import type { ExpandedConfig } from "./config.js"; | ||
@@ -16,4 +16,6 @@ export * from "./api.js"; | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
batch(mode: TransactionMode, stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
transaction(): Promise<Transaction>; | ||
transaction(mode?: TransactionMode): Promise<Transaction>; | ||
executeMultiple(sql: string): Promise<void>; | ||
close(): void; | ||
@@ -27,2 +29,4 @@ } | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
executeMultiple(sql: string): Promise<void>; | ||
rollback(): Promise<void>; | ||
@@ -29,0 +33,0 @@ commit(): Promise<void>; |
@@ -5,3 +5,3 @@ import Database from "better-sqlite3"; | ||
import { expandConfig } from "./config.js"; | ||
import { supportedUrlLink } from "./help.js"; | ||
import { supportedUrlLink, transactionModeToBegin, extractBatchArgs } from "./util.js"; | ||
export * from "./api.js"; | ||
@@ -64,13 +64,10 @@ export function createClient(config) { | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = extractBatchArgs(arg1, arg2); | ||
this.#checkNotClosed(); | ||
const db = new Database(this.path, this.options); | ||
try { | ||
if (stmts.length > 1) { | ||
executeStmt(db, "BEGIN"); | ||
} | ||
executeStmt(db, transactionModeToBegin(mode)); | ||
const resultSets = stmts.map(stmt => executeStmt(db, stmt)); | ||
if (stmts.length > 1) { | ||
executeStmt(db, "COMMIT"); | ||
} | ||
executeStmt(db, "COMMIT"); | ||
return resultSets; | ||
@@ -82,7 +79,7 @@ } | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
this.#checkNotClosed(); | ||
const db = new Database(this.path, this.options); | ||
try { | ||
executeStmt(db, "BEGIN"); | ||
executeStmt(db, transactionModeToBegin(mode)); | ||
return new Sqlite3Transaction(db); | ||
@@ -95,2 +92,12 @@ } | ||
} | ||
async executeMultiple(sql) { | ||
this.#checkNotClosed(); | ||
const db = new Database(this.path, this.options); | ||
try { | ||
return executeMultiple(db, sql); | ||
} | ||
finally { | ||
db.close(); | ||
} | ||
} | ||
close() { | ||
@@ -115,2 +122,10 @@ this.closed = true; | ||
} | ||
async batch(stmts) { | ||
this.#checkNotClosed(); | ||
return stmts.map(stmt => executeStmt(this.database, stmt)); | ||
} | ||
async executeMultiple(sql) { | ||
this.#checkNotClosed(); | ||
return executeMultiple(this.database, sql); | ||
} | ||
async rollback() { | ||
@@ -187,6 +202,3 @@ if (!this.database.open) { | ||
catch (e) { | ||
if (e instanceof Database.SqliteError) { | ||
throw new LibsqlError(e.message, e.code, e); | ||
} | ||
throw e; | ||
throw mapSqliteError(e); | ||
} | ||
@@ -245,1 +257,15 @@ } | ||
const maxInteger = 9223372036854775807n; | ||
function executeMultiple(db, sql) { | ||
try { | ||
db.exec(sql); | ||
} | ||
catch (e) { | ||
throw mapSqliteError(e); | ||
} | ||
} | ||
function mapSqliteError(e) { | ||
if (e instanceof Database.SqliteError) { | ||
return new LibsqlError(e.message, e.code, e); | ||
} | ||
return e; | ||
} |
import { LibsqlError } from "./api.js"; | ||
import { expandConfig } from "./config.js"; | ||
import { supportedUrlLink } from "./help.js"; | ||
import { supportedUrlLink } from "./util.js"; | ||
import { _createClient as _createWsClient } from "./ws.js"; | ||
@@ -5,0 +5,0 @@ import { _createClient as _createHttpClient } from "./http.js"; |
/// <reference types="node" /> | ||
import * as hrana from "@libsql/hrana-client"; | ||
import type { Config, Client, Transaction, ResultSet, InStatement } from "./api.js"; | ||
import { TransactionMode } from "./api.js"; | ||
import type { ExpandedConfig } from "./config.js"; | ||
@@ -28,4 +29,6 @@ import { HranaTransaction } from "./hrana.js"; | ||
execute(stmt: InStatement): Promise<ResultSet>; | ||
batch(mode: TransactionMode, stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
batch(stmts: Array<InStatement>): Promise<Array<ResultSet>>; | ||
transaction(): Promise<WsTransaction>; | ||
transaction(mode?: TransactionMode): Promise<WsTransaction>; | ||
executeMultiple(sql: string): Promise<void>; | ||
_closeStream(streamState: StreamState): void; | ||
@@ -37,3 +40,3 @@ close(): void; | ||
/** @private */ | ||
constructor(client: WsClient, state: StreamState); | ||
constructor(client: WsClient, state: StreamState, mode: TransactionMode); | ||
/** @private */ | ||
@@ -40,0 +43,0 @@ _getStream(): hrana.Stream; |
import * as hrana from "@libsql/hrana-client"; | ||
import { LibsqlError } from "./api.js"; | ||
import { expandConfig } from "./config.js"; | ||
import { supportedUrlLink } from "./help.js"; | ||
import { HranaTransaction, executeHranaBatch, stmtToHrana, resultSetFromHrana, mapHranaError, } from "./hrana.js"; | ||
import { SqlCache } from "./sql_cache.js"; | ||
import { encodeBaseUrl } from "./uri.js"; | ||
import { supportedUrlLink, extractBatchArgs } from "./util.js"; | ||
export * from "./api.js"; | ||
@@ -78,3 +78,4 @@ export function createClient(config) { | ||
} | ||
async batch(stmts) { | ||
async batch(arg1, arg2 = undefined) { | ||
const { mode, stmts } = extractBatchArgs(arg1, arg2); | ||
const streamState = await this.#openStream(); | ||
@@ -87,3 +88,3 @@ try { | ||
const batch = streamState.stream.batch(); | ||
const resultsPromise = executeHranaBatch(batch, hranaStmts); | ||
const resultsPromise = executeHranaBatch(mode, batch, hranaStmts); | ||
streamState.stream.close(); | ||
@@ -99,3 +100,3 @@ return await resultsPromise; | ||
} | ||
async transaction() { | ||
async transaction(mode = "write") { | ||
const streamState = await this.#openStream(); | ||
@@ -105,3 +106,3 @@ try { | ||
// network roundtrip | ||
return new WsTransaction(this, streamState); | ||
return new WsTransaction(this, streamState, mode); | ||
} | ||
@@ -113,2 +114,18 @@ catch (e) { | ||
} | ||
async executeMultiple(sql) { | ||
const streamState = await this.#openStream(); | ||
try { | ||
// Schedule all operations synchronously, so they will be pipelined and executed in a single | ||
// network roundtrip. | ||
const promise = streamState.stream.sequence(sql); | ||
streamState.stream.close(); | ||
await promise; | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
finally { | ||
this._closeStream(streamState); | ||
} | ||
} | ||
async #openStream() { | ||
@@ -222,4 +239,4 @@ if (this.closed) { | ||
/** @private */ | ||
constructor(client, state) { | ||
super(); | ||
constructor(client, state, mode) { | ||
super(mode); | ||
this.#client = client; | ||
@@ -226,0 +243,0 @@ this.#streamState = state; |
{ | ||
"name": "@libsql/client", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"keywords": [ | ||
@@ -90,3 +90,3 @@ "libsql", | ||
"dependencies": { | ||
"@libsql/hrana-client": "^0.4.0", | ||
"@libsql/hrana-client": "^0.4.1", | ||
"better-sqlite3": "^8.0.1", | ||
@@ -93,0 +93,0 @@ "js-base64": "^3.7.5" |
@@ -52,4 +52,2 @@ # JavaScript & TypeScript SDK for libSQL | ||
In each case, the client API is the same, with the exception that [HTTP URLs](#http-urls) don't support interactive transactions. | ||
### Local SQLite files | ||
@@ -56,0 +54,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
135732
3347
143
Updated@libsql/hrana-client@^0.4.1