@libsql/client
Advanced tools
Comparing version 0.3.1 to 0.3.2-pre.0
@@ -32,2 +32,3 @@ "use strict"; | ||
#mode; | ||
#version; | ||
// Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the | ||
@@ -37,54 +38,9 @@ // BEGIN statement yet. | ||
/** @private */ | ||
constructor(mode) { | ||
constructor(mode, version) { | ||
this.#mode = mode; | ||
this.#version = version; | ||
this.#started = undefined; | ||
} | ||
async execute(stmt) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new api_js_1.LibsqlError("Cannot execute a statement because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
const hranaStmt = stmtToHrana(stmt); | ||
let rowsPromise; | ||
if (this.#started === undefined) { | ||
// The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with | ||
// `stmt`. | ||
this._getSqlCache().apply([hranaStmt]); | ||
const batch = stream.batch(); | ||
const beginStep = batch.step(); | ||
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 | ||
// outside of a transaction. | ||
rowsPromise = batch.step() | ||
.condition(hrana.BatchCond.ok(beginStep)) | ||
.query(hranaStmt) | ||
.then((result) => result); | ||
// `this.#started` is resolved successfully only if the batch and the BEGIN statement inside | ||
// of the batch are both successful. | ||
this.#started = batch.execute() | ||
.then(() => beginPromise) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
// If the BEGIN failed, the transaction is unusable and we must close it. However, if the | ||
// BEGIN suceeds and `stmt` fails, the transaction is _not_ closed. | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
// The transaction has started, so we must wait until the BEGIN statement completed to make | ||
// sure that we don't execute `stmt` outside of a transaction. | ||
await this.#started; | ||
this._getSqlCache().apply([hranaStmt]); | ||
rowsPromise = stream.query(hranaStmt); | ||
} | ||
return resultSetFromHrana(await rowsPromise); | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
execute(stmt) { | ||
return this.batch([stmt]).then((results) => results[0]); | ||
} | ||
@@ -94,13 +50,16 @@ async batch(stmts) { | ||
if (stream.closed) { | ||
throw new api_js_1.LibsqlError("Cannot execute a batch because the transaction is closed", "TRANSACTION_CLOSED"); | ||
throw new api_js_1.LibsqlError("Cannot execute statements 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) { | ||
// The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with | ||
// `hranaStmts`. | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const batch = stream.batch(this.#version >= 3); | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run((0, util_js_1.transactionModeToBegin)(this.#mode)); | ||
// Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it | ||
// outside of a transaction. | ||
let lastStep = beginStep; | ||
@@ -110,6 +69,13 @@ rowsPromises = hranaStmts.map((hranaStmt) => { | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (this.#version >= 3) { | ||
// If the Hrana version supports it, make sure that we are still in a transaction | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
rowsPromise.catch(() => undefined); // silence Node warning | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
// `this.#started` is resolved successfully only if the batch and the BEGIN statement inside | ||
// of the batch are both successful. | ||
this.#started = batch.execute() | ||
@@ -122,2 +88,4 @@ .then(() => beginPromise) | ||
catch (e) { | ||
// If the BEGIN failed, the transaction is unusable and we must close it. However, if the | ||
// BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. | ||
this.close(); | ||
@@ -128,5 +96,14 @@ throw e; | ||
else { | ||
await this.#started; | ||
if (this.#version < 3) { | ||
// The transaction has started, so we must wait until the BEGIN statement completed to make | ||
// sure that we don't execute `hranaStmts` outside of a transaction. | ||
await this.#started; | ||
} | ||
else { | ||
// The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make | ||
// sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to | ||
// wait for `this.#started` | ||
} | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const batch = stream.batch(this.#version >= 3); | ||
let lastStep = undefined; | ||
@@ -138,3 +115,7 @@ rowsPromises = hranaStmts.map((hranaStmt) => { | ||
} | ||
if (this.#version >= 3) { | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
rowsPromise.catch(() => undefined); // silence Node warning | ||
lastStep = stmtStep; | ||
@@ -149,3 +130,4 @@ return rowsPromise; | ||
if (rows === undefined) { | ||
throw new api_js_1.LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
throw new api_js_1.LibsqlError("Statement in a transaction was not executed, " + | ||
"probably because the transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
@@ -205,3 +187,3 @@ resultSets.push(resultSetFromHrana(rows)); | ||
.catch(e => { throw mapHranaError(e); }); | ||
stream.close(); | ||
stream.closeGracefully(); | ||
await promise; | ||
@@ -236,3 +218,3 @@ } | ||
.catch(e => { throw mapHranaError(e); }); | ||
stream.close(); | ||
stream.closeGracefully(); | ||
await promise; | ||
@@ -249,3 +231,3 @@ } | ||
exports.HranaTransaction = HranaTransaction; | ||
async function executeHranaBatch(mode, batch, hranaStmts) { | ||
async function executeHranaBatch(mode, version, batch, hranaStmts) { | ||
const beginStep = batch.step(); | ||
@@ -257,2 +239,5 @@ const beginPromise = beginStep.run((0, util_js_1.transactionModeToBegin)(mode)); | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (version >= 3) { | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const stmtPromise = stmtStep.query(hranaStmt); | ||
@@ -264,2 +249,5 @@ lastStep = stmtStep; | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (version >= 3) { | ||
commitStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const commitPromise = commitStep.run("COMMIT"); | ||
@@ -275,3 +263,3 @@ const rollbackStep = batch.step() | ||
if (hranaRows === undefined) { | ||
throw new api_js_1.LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
throw new api_js_1.LibsqlError("Statement in a batch was not executed, probably because the transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
@@ -305,3 +293,3 @@ resultSets.push(resultSetFromHrana(hranaRows)); | ||
const lastInsertRowid = hranaRows.lastInsertRowid !== undefined | ||
? BigInt(hranaRows.lastInsertRowid) : undefined; | ||
? hranaRows.lastInsertRowid : undefined; | ||
return new util_js_1.ResultSetImpl(columns, rows, rowsAffected, lastInsertRowid); | ||
@@ -312,21 +300,3 @@ } | ||
if (e instanceof hrana.ClientError) { | ||
let code = "UNKNOWN"; | ||
if (e instanceof hrana.ResponseError && e.code !== undefined) { | ||
code = e.code; | ||
} | ||
else if (e instanceof hrana.ProtoError) { | ||
code = "HRANA_PROTO_ERROR"; | ||
} | ||
else if (e instanceof hrana.ClosedError) { | ||
code = "HRANA_CLOSED_ERROR"; | ||
} | ||
else if (e instanceof hrana.WebSocketError) { | ||
code = "HRANA_WEBSOCKET_ERROR"; | ||
} | ||
else if (e instanceof hrana.HttpServerError) { | ||
code = "SERVER_ERROR"; | ||
} | ||
else if (e instanceof hrana.ProtocolVersionError) { | ||
code = "PROTOCOL_VERSION_ERROR"; | ||
} | ||
const code = mapHranaErrorCode(e); | ||
return new api_js_1.LibsqlError(e.message, code, e); | ||
@@ -337,1 +307,28 @@ } | ||
exports.mapHranaError = mapHranaError; | ||
function mapHranaErrorCode(e) { | ||
if (e instanceof hrana.ResponseError && e.code !== undefined) { | ||
return e.code; | ||
} | ||
else if (e instanceof hrana.ProtoError) { | ||
return "HRANA_PROTO_ERROR"; | ||
} | ||
else if (e instanceof hrana.ClosedError) { | ||
return e.cause instanceof hrana.ClientError | ||
? mapHranaErrorCode(e.cause) : "HRANA_CLOSED_ERROR"; | ||
} | ||
else if (e instanceof hrana.WebSocketError) { | ||
return "HRANA_WEBSOCKET_ERROR"; | ||
} | ||
else if (e instanceof hrana.HttpServerError) { | ||
return "SERVER_ERROR"; | ||
} | ||
else if (e instanceof hrana.ProtocolVersionError) { | ||
return "PROTOCOL_VERSION_ERROR"; | ||
} | ||
else if (e instanceof hrana.InternalError) { | ||
return "INTERNAL_ERROR"; | ||
} | ||
else { | ||
return "UNKNOWN"; | ||
} | ||
} |
@@ -79,3 +79,3 @@ "use strict"; | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -91,2 +91,3 @@ return (0, hrana_js_1.resultSetFromHrana)(await rowsPromise); | ||
const hranaStmts = stmts.map(hrana_js_1.stmtToHrana); | ||
const version = await this.#client.getVersion(); | ||
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and | ||
@@ -101,7 +102,11 @@ // close the stream in a single HTTP request. | ||
sqlCache.apply(hranaStmts); | ||
const batch = stream.batch(); | ||
resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, batch, hranaStmts); | ||
// TODO: we do not use a cursor here, because it would cause three roundtrips: | ||
// 1. pipeline request to store SQL texts | ||
// 2. cursor request | ||
// 3. pipeline request to close the stream | ||
const batch = stream.batch(false); | ||
resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, version, batch, hranaStmts); | ||
} | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -116,3 +121,4 @@ return await resultsPromise; | ||
try { | ||
return new HttpTransaction(this.#client.openStream(), mode); | ||
const version = await this.#client.getVersion(); | ||
return new HttpTransaction(this.#client.openStream(), mode, version); | ||
} | ||
@@ -133,3 +139,3 @@ catch (e) { | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -154,4 +160,4 @@ await promise; | ||
/** @private */ | ||
constructor(stream, mode) { | ||
super(mode); | ||
constructor(stream, mode, version) { | ||
super(mode, version); | ||
this.#stream = stream; | ||
@@ -158,0 +164,0 @@ this.#sqlCache = new sql_cache_js_1.SqlCache(stream, sqlCacheCapacity); |
@@ -94,3 +94,8 @@ "use strict"; | ||
executeStmt(db, (0, util_js_1.transactionModeToBegin)(mode), this.#intMode); | ||
const resultSets = stmts.map(stmt => executeStmt(db, stmt, this.#intMode)); | ||
const resultSets = stmts.map((stmt) => { | ||
if (!db.inTransaction) { | ||
throw new api_js_1.LibsqlError("The transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
return executeStmt(db, stmt, this.#intMode); | ||
}); | ||
executeStmt(db, "COMMIT", this.#intMode); | ||
@@ -148,4 +153,6 @@ return resultSets; | ||
async batch(stmts) { | ||
this.#checkNotClosed(); | ||
return stmts.map(stmt => executeStmt(this.#database, stmt, this.#intMode)); | ||
return stmts.map((stmt) => { | ||
this.#checkNotClosed(); | ||
return executeStmt(this.#database, stmt, this.#intMode); | ||
}); | ||
} | ||
@@ -160,2 +167,3 @@ async executeMultiple(sql) { | ||
} | ||
this.#checkNotClosed(); | ||
executeStmt(this.#database, "ROLLBACK", this.#intMode); | ||
@@ -176,3 +184,3 @@ this.#database.close(); | ||
#checkNotClosed() { | ||
if (!this.#database.open) { | ||
if (!this.#database.open || !this.#database.inTransaction) { | ||
throw new api_js_1.LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); | ||
@@ -179,0 +187,0 @@ } |
@@ -103,3 +103,3 @@ "use strict"; | ||
const hranaRowsPromise = streamState.stream.query(hranaStmt); | ||
streamState.stream.close(); | ||
streamState.stream.closeGracefully(); | ||
return (0, hrana_js_1.resultSetFromHrana)(await hranaRowsPromise); | ||
@@ -118,8 +118,8 @@ } | ||
const hranaStmts = stmts.map(hrana_js_1.stmtToHrana); | ||
const version = await streamState.conn.client.getVersion(); | ||
// Schedule all operations synchronously, so they will be pipelined and executed in a single | ||
// network roundtrip. | ||
streamState.conn.sqlCache.apply(hranaStmts); | ||
const batch = streamState.stream.batch(); | ||
const resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, batch, hranaStmts); | ||
streamState.stream.close(); | ||
const batch = streamState.stream.batch(version >= 3); | ||
const resultsPromise = (0, hrana_js_1.executeHranaBatch)(mode, version, batch, hranaStmts); | ||
return await resultsPromise; | ||
@@ -137,5 +137,6 @@ } | ||
try { | ||
const version = await streamState.conn.client.getVersion(); | ||
// the BEGIN statement will be batched with the first statement on the transaction to save a | ||
// network roundtrip | ||
return new WsTransaction(this, streamState, mode); | ||
return new WsTransaction(this, streamState, mode, version); | ||
} | ||
@@ -153,3 +154,3 @@ catch (e) { | ||
const promise = streamState.stream.sequence(sql); | ||
streamState.stream.close(); | ||
streamState.stream.closeGracefully(); | ||
await promise; | ||
@@ -274,4 +275,4 @@ } | ||
/** @private */ | ||
constructor(client, state, mode) { | ||
super(mode); | ||
constructor(client, state, mode, version) { | ||
super(mode, version); | ||
this.#client = client; | ||
@@ -278,0 +279,0 @@ this.#streamState = state; |
@@ -7,3 +7,3 @@ import * as hrana from "@libsql/hrana-client"; | ||
/** @private */ | ||
constructor(mode: TransactionMode); | ||
constructor(mode: TransactionMode, version: hrana.ProtocolVersion); | ||
/** @private */ | ||
@@ -21,5 +21,5 @@ abstract _getStream(): hrana.Stream; | ||
} | ||
export declare function executeHranaBatch(mode: TransactionMode, batch: hrana.Batch, hranaStmts: Array<hrana.Stmt>): Promise<Array<ResultSet>>; | ||
export declare function executeHranaBatch(mode: TransactionMode, version: hrana.ProtocolVersion, 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; |
@@ -6,2 +6,3 @@ import * as hrana from "@libsql/hrana-client"; | ||
#mode; | ||
#version; | ||
// Promise that is resolved when the BEGIN statement completes, or `undefined` if we haven't executed the | ||
@@ -11,54 +12,9 @@ // BEGIN statement yet. | ||
/** @private */ | ||
constructor(mode) { | ||
constructor(mode, version) { | ||
this.#mode = mode; | ||
this.#version = version; | ||
this.#started = undefined; | ||
} | ||
async execute(stmt) { | ||
const stream = this._getStream(); | ||
if (stream.closed) { | ||
throw new LibsqlError("Cannot execute a statement because the transaction is closed", "TRANSACTION_CLOSED"); | ||
} | ||
try { | ||
const hranaStmt = stmtToHrana(stmt); | ||
let rowsPromise; | ||
if (this.#started === undefined) { | ||
// The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with | ||
// `stmt`. | ||
this._getSqlCache().apply([hranaStmt]); | ||
const batch = stream.batch(); | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); | ||
// Execute the `stmt` only if the BEGIN succeeded, to make sure that we don't execute it | ||
// outside of a transaction. | ||
rowsPromise = batch.step() | ||
.condition(hrana.BatchCond.ok(beginStep)) | ||
.query(hranaStmt) | ||
.then((result) => result); | ||
// `this.#started` is resolved successfully only if the batch and the BEGIN statement inside | ||
// of the batch are both successful. | ||
this.#started = batch.execute() | ||
.then(() => beginPromise) | ||
.then(() => undefined); | ||
try { | ||
await this.#started; | ||
} | ||
catch (e) { | ||
// If the BEGIN failed, the transaction is unusable and we must close it. However, if the | ||
// BEGIN suceeds and `stmt` fails, the transaction is _not_ closed. | ||
this.close(); | ||
throw e; | ||
} | ||
} | ||
else { | ||
// The transaction has started, so we must wait until the BEGIN statement completed to make | ||
// sure that we don't execute `stmt` outside of a transaction. | ||
await this.#started; | ||
this._getSqlCache().apply([hranaStmt]); | ||
rowsPromise = stream.query(hranaStmt); | ||
} | ||
return resultSetFromHrana(await rowsPromise); | ||
} | ||
catch (e) { | ||
throw mapHranaError(e); | ||
} | ||
execute(stmt) { | ||
return this.batch([stmt]).then((results) => results[0]); | ||
} | ||
@@ -68,13 +24,16 @@ async batch(stmts) { | ||
if (stream.closed) { | ||
throw new LibsqlError("Cannot execute a batch because the transaction is closed", "TRANSACTION_CLOSED"); | ||
throw new LibsqlError("Cannot execute statements 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) { | ||
// The transaction hasn't started yet, so we need to send the BEGIN statement in a batch with | ||
// `hranaStmts`. | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const batch = stream.batch(this.#version >= 3); | ||
const beginStep = batch.step(); | ||
const beginPromise = beginStep.run(transactionModeToBegin(this.#mode)); | ||
// Execute the `hranaStmts` only if the BEGIN succeeded, to make sure that we don't execute it | ||
// outside of a transaction. | ||
let lastStep = beginStep; | ||
@@ -84,6 +43,13 @@ rowsPromises = hranaStmts.map((hranaStmt) => { | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (this.#version >= 3) { | ||
// If the Hrana version supports it, make sure that we are still in a transaction | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
rowsPromise.catch(() => undefined); // silence Node warning | ||
lastStep = stmtStep; | ||
return rowsPromise; | ||
}); | ||
// `this.#started` is resolved successfully only if the batch and the BEGIN statement inside | ||
// of the batch are both successful. | ||
this.#started = batch.execute() | ||
@@ -96,2 +62,4 @@ .then(() => beginPromise) | ||
catch (e) { | ||
// If the BEGIN failed, the transaction is unusable and we must close it. However, if the | ||
// BEGIN suceeds and `hranaStmts` fail, the transaction is _not_ closed. | ||
this.close(); | ||
@@ -102,5 +70,14 @@ throw e; | ||
else { | ||
await this.#started; | ||
if (this.#version < 3) { | ||
// The transaction has started, so we must wait until the BEGIN statement completed to make | ||
// sure that we don't execute `hranaStmts` outside of a transaction. | ||
await this.#started; | ||
} | ||
else { | ||
// The transaction has started, but we will use `hrana.BatchCond.isAutocommit()` to make | ||
// sure that we don't execute `hranaStmts` outside of a transaction, so we don't have to | ||
// wait for `this.#started` | ||
} | ||
this._getSqlCache().apply(hranaStmts); | ||
const batch = stream.batch(); | ||
const batch = stream.batch(this.#version >= 3); | ||
let lastStep = undefined; | ||
@@ -112,3 +89,7 @@ rowsPromises = hranaStmts.map((hranaStmt) => { | ||
} | ||
if (this.#version >= 3) { | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const rowsPromise = stmtStep.query(hranaStmt); | ||
rowsPromise.catch(() => undefined); // silence Node warning | ||
lastStep = stmtStep; | ||
@@ -123,3 +104,4 @@ return rowsPromise; | ||
if (rows === undefined) { | ||
throw new LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
throw new LibsqlError("Statement in a transaction was not executed, " + | ||
"probably because the transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
@@ -179,3 +161,3 @@ resultSets.push(resultSetFromHrana(rows)); | ||
.catch(e => { throw mapHranaError(e); }); | ||
stream.close(); | ||
stream.closeGracefully(); | ||
await promise; | ||
@@ -210,3 +192,3 @@ } | ||
.catch(e => { throw mapHranaError(e); }); | ||
stream.close(); | ||
stream.closeGracefully(); | ||
await promise; | ||
@@ -222,3 +204,3 @@ } | ||
} | ||
export async function executeHranaBatch(mode, batch, hranaStmts) { | ||
export async function executeHranaBatch(mode, version, batch, hranaStmts) { | ||
const beginStep = batch.step(); | ||
@@ -230,2 +212,5 @@ const beginPromise = beginStep.run(transactionModeToBegin(mode)); | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (version >= 3) { | ||
stmtStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const stmtPromise = stmtStep.query(hranaStmt); | ||
@@ -237,2 +222,5 @@ lastStep = stmtStep; | ||
.condition(hrana.BatchCond.ok(lastStep)); | ||
if (version >= 3) { | ||
commitStep.condition(hrana.BatchCond.not(hrana.BatchCond.isAutocommit(batch))); | ||
} | ||
const commitPromise = commitStep.run("COMMIT"); | ||
@@ -248,3 +236,3 @@ const rollbackStep = batch.step() | ||
if (hranaRows === undefined) { | ||
throw new LibsqlError("Server did not return a result for statement in a batch", "SERVER_ERROR"); | ||
throw new LibsqlError("Statement in a batch was not executed, probably because the transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
@@ -276,3 +264,3 @@ resultSets.push(resultSetFromHrana(hranaRows)); | ||
const lastInsertRowid = hranaRows.lastInsertRowid !== undefined | ||
? BigInt(hranaRows.lastInsertRowid) : undefined; | ||
? hranaRows.lastInsertRowid : undefined; | ||
return new ResultSetImpl(columns, rows, rowsAffected, lastInsertRowid); | ||
@@ -282,21 +270,3 @@ } | ||
if (e instanceof hrana.ClientError) { | ||
let code = "UNKNOWN"; | ||
if (e instanceof hrana.ResponseError && e.code !== undefined) { | ||
code = e.code; | ||
} | ||
else if (e instanceof hrana.ProtoError) { | ||
code = "HRANA_PROTO_ERROR"; | ||
} | ||
else if (e instanceof hrana.ClosedError) { | ||
code = "HRANA_CLOSED_ERROR"; | ||
} | ||
else if (e instanceof hrana.WebSocketError) { | ||
code = "HRANA_WEBSOCKET_ERROR"; | ||
} | ||
else if (e instanceof hrana.HttpServerError) { | ||
code = "SERVER_ERROR"; | ||
} | ||
else if (e instanceof hrana.ProtocolVersionError) { | ||
code = "PROTOCOL_VERSION_ERROR"; | ||
} | ||
const code = mapHranaErrorCode(e); | ||
return new LibsqlError(e.message, code, e); | ||
@@ -306,1 +276,28 @@ } | ||
} | ||
function mapHranaErrorCode(e) { | ||
if (e instanceof hrana.ResponseError && e.code !== undefined) { | ||
return e.code; | ||
} | ||
else if (e instanceof hrana.ProtoError) { | ||
return "HRANA_PROTO_ERROR"; | ||
} | ||
else if (e instanceof hrana.ClosedError) { | ||
return e.cause instanceof hrana.ClientError | ||
? mapHranaErrorCode(e.cause) : "HRANA_CLOSED_ERROR"; | ||
} | ||
else if (e instanceof hrana.WebSocketError) { | ||
return "HRANA_WEBSOCKET_ERROR"; | ||
} | ||
else if (e instanceof hrana.HttpServerError) { | ||
return "SERVER_ERROR"; | ||
} | ||
else if (e instanceof hrana.ProtocolVersionError) { | ||
return "PROTOCOL_VERSION_ERROR"; | ||
} | ||
else if (e instanceof hrana.InternalError) { | ||
return "INTERNAL_ERROR"; | ||
} | ||
else { | ||
return "UNKNOWN"; | ||
} | ||
} |
@@ -28,3 +28,3 @@ /// <reference types="node" /> | ||
/** @private */ | ||
constructor(stream: hrana.HttpStream, mode: TransactionMode); | ||
constructor(stream: hrana.HttpStream, mode: TransactionMode, version: hrana.ProtocolVersion); | ||
/** @private */ | ||
@@ -31,0 +31,0 @@ _getStream(): hrana.Stream; |
@@ -48,3 +48,3 @@ import * as hrana from "@libsql/hrana-client"; | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -60,2 +60,3 @@ return resultSetFromHrana(await rowsPromise); | ||
const hranaStmts = stmts.map(stmtToHrana); | ||
const version = await this.#client.getVersion(); | ||
// Pipeline all operations, so `hrana.HttpClient` can open the stream, execute the batch and | ||
@@ -70,7 +71,11 @@ // close the stream in a single HTTP request. | ||
sqlCache.apply(hranaStmts); | ||
const batch = stream.batch(); | ||
resultsPromise = executeHranaBatch(mode, batch, hranaStmts); | ||
// TODO: we do not use a cursor here, because it would cause three roundtrips: | ||
// 1. pipeline request to store SQL texts | ||
// 2. cursor request | ||
// 3. pipeline request to close the stream | ||
const batch = stream.batch(false); | ||
resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); | ||
} | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -85,3 +90,4 @@ return await resultsPromise; | ||
try { | ||
return new HttpTransaction(this.#client.openStream(), mode); | ||
const version = await this.#client.getVersion(); | ||
return new HttpTransaction(this.#client.openStream(), mode, version); | ||
} | ||
@@ -102,3 +108,3 @@ catch (e) { | ||
finally { | ||
stream.close(); | ||
stream.closeGracefully(); | ||
} | ||
@@ -122,4 +128,4 @@ await promise; | ||
/** @private */ | ||
constructor(stream, mode) { | ||
super(mode); | ||
constructor(stream, mode, version) { | ||
super(mode, version); | ||
this.#stream = stream; | ||
@@ -126,0 +132,0 @@ this.#sqlCache = new SqlCache(stream, sqlCacheCapacity); |
@@ -72,3 +72,8 @@ import Database from "better-sqlite3"; | ||
executeStmt(db, transactionModeToBegin(mode), this.#intMode); | ||
const resultSets = stmts.map(stmt => executeStmt(db, stmt, this.#intMode)); | ||
const resultSets = stmts.map((stmt) => { | ||
if (!db.inTransaction) { | ||
throw new LibsqlError("The transaction has been rolled back", "TRANSACTION_CLOSED"); | ||
} | ||
return executeStmt(db, stmt, this.#intMode); | ||
}); | ||
executeStmt(db, "COMMIT", this.#intMode); | ||
@@ -125,4 +130,6 @@ return resultSets; | ||
async batch(stmts) { | ||
this.#checkNotClosed(); | ||
return stmts.map(stmt => executeStmt(this.#database, stmt, this.#intMode)); | ||
return stmts.map((stmt) => { | ||
this.#checkNotClosed(); | ||
return executeStmt(this.#database, stmt, this.#intMode); | ||
}); | ||
} | ||
@@ -137,2 +144,3 @@ async executeMultiple(sql) { | ||
} | ||
this.#checkNotClosed(); | ||
executeStmt(this.#database, "ROLLBACK", this.#intMode); | ||
@@ -153,3 +161,3 @@ this.#database.close(); | ||
#checkNotClosed() { | ||
if (!this.#database.open) { | ||
if (!this.#database.open || !this.#database.inTransaction) { | ||
throw new LibsqlError("The transaction is closed", "TRANSACTION_CLOSED"); | ||
@@ -156,0 +164,0 @@ } |
@@ -39,3 +39,3 @@ /// <reference types="node" /> | ||
/** @private */ | ||
constructor(client: WsClient, state: StreamState, mode: TransactionMode); | ||
constructor(client: WsClient, state: StreamState, mode: TransactionMode, version: hrana.ProtocolVersion); | ||
/** @private */ | ||
@@ -42,0 +42,0 @@ _getStream(): hrana.Stream; |
@@ -72,3 +72,3 @@ import * as hrana from "@libsql/hrana-client"; | ||
const hranaRowsPromise = streamState.stream.query(hranaStmt); | ||
streamState.stream.close(); | ||
streamState.stream.closeGracefully(); | ||
return resultSetFromHrana(await hranaRowsPromise); | ||
@@ -87,8 +87,8 @@ } | ||
const hranaStmts = stmts.map(stmtToHrana); | ||
const version = await streamState.conn.client.getVersion(); | ||
// Schedule all operations synchronously, so they will be pipelined and executed in a single | ||
// network roundtrip. | ||
streamState.conn.sqlCache.apply(hranaStmts); | ||
const batch = streamState.stream.batch(); | ||
const resultsPromise = executeHranaBatch(mode, batch, hranaStmts); | ||
streamState.stream.close(); | ||
const batch = streamState.stream.batch(version >= 3); | ||
const resultsPromise = executeHranaBatch(mode, version, batch, hranaStmts); | ||
return await resultsPromise; | ||
@@ -106,5 +106,6 @@ } | ||
try { | ||
const version = await streamState.conn.client.getVersion(); | ||
// the BEGIN statement will be batched with the first statement on the transaction to save a | ||
// network roundtrip | ||
return new WsTransaction(this, streamState, mode); | ||
return new WsTransaction(this, streamState, mode, version); | ||
} | ||
@@ -122,3 +123,3 @@ catch (e) { | ||
const promise = streamState.stream.sequence(sql); | ||
streamState.stream.close(); | ||
streamState.stream.closeGracefully(); | ||
await promise; | ||
@@ -242,4 +243,4 @@ } | ||
/** @private */ | ||
constructor(client, state, mode) { | ||
super(mode); | ||
constructor(client, state, mode, version) { | ||
super(mode, version); | ||
this.#client = client; | ||
@@ -246,0 +247,0 @@ this.#streamState = state; |
{ | ||
"name": "@libsql/client", | ||
"version": "0.3.1", | ||
"version": "0.3.2-pre.0", | ||
"keywords": [ | ||
@@ -86,2 +86,7 @@ "libsql", | ||
"dependencies": { | ||
"@libsql/hrana-client": "^0.5.0-pre.2", | ||
"better-sqlite3": "^8.0.1", | ||
"js-base64": "^3.7.5" | ||
}, | ||
"devDependencies": { | ||
@@ -95,8 +100,3 @@ "@types/better-sqlite3": "^7.6.3", | ||
"typescript": "^4.9.4" | ||
}, | ||
"dependencies": { | ||
"@libsql/hrana-client": "^0.4.3", | ||
"better-sqlite3": "^8.0.1", | ||
"js-base64": "^3.7.5" | ||
} | ||
} |
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
144687
3518
+ Added@libsql/hrana-client@0.5.6(transitive)
+ Added@types/node@22.9.3(transitive)
+ Addeddata-uri-to-buffer@4.0.1(transitive)
+ Addedfetch-blob@3.2.0(transitive)
+ Addedformdata-polyfill@4.0.10(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addednode-fetch@3.3.2(transitive)
+ Addedundici-types@6.19.8(transitive)
+ Addedweb-streams-polyfill@3.3.3(transitive)
- Removed@libsql/hrana-client@0.4.4(transitive)
- Removed@types/node@22.10.0(transitive)
- Removedundici-types@6.20.0(transitive)