mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 00:54:19 +01:00
javascript: Implement transactions API
This commit is contained in:
@@ -38,6 +38,7 @@ class Database {
|
||||
db: NativeDB;
|
||||
memory: boolean;
|
||||
open: boolean;
|
||||
private _inTransaction: boolean = false;
|
||||
|
||||
/**
|
||||
* Creates a new database connection. If the database file pointed to by `path` does not exists, it will be created.
|
||||
@@ -61,9 +62,7 @@ class Database {
|
||||
|
||||
Object.defineProperties(this, {
|
||||
inTransaction: {
|
||||
get() {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
get: () => this._inTransaction,
|
||||
},
|
||||
name: {
|
||||
get() {
|
||||
@@ -117,12 +116,15 @@ class Database {
|
||||
const wrapTxn = (mode) => {
|
||||
return (...bindParameters) => {
|
||||
db.exec("BEGIN " + mode);
|
||||
db._inTransaction = true;
|
||||
try {
|
||||
const result = fn(...bindParameters);
|
||||
db.exec("COMMIT");
|
||||
db._inTransaction = false;
|
||||
return result;
|
||||
} catch (err) {
|
||||
db.exec("ROLLBACK");
|
||||
db._inTransaction = false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ class Database {
|
||||
db: NativeDB;
|
||||
memory: boolean;
|
||||
open: boolean;
|
||||
private _inTransaction: boolean = false;
|
||||
/**
|
||||
* Creates a new database connection. If the database file pointed to by `path` does not exists, it will be created.
|
||||
*
|
||||
@@ -65,9 +66,7 @@ class Database {
|
||||
this.memory = db.memory;
|
||||
Object.defineProperties(this, {
|
||||
inTransaction: {
|
||||
get() {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
get: () => this._inTransaction,
|
||||
},
|
||||
name: {
|
||||
get() {
|
||||
@@ -121,12 +120,15 @@ class Database {
|
||||
const wrapTxn = (mode) => {
|
||||
return (...bindParameters) => {
|
||||
db.exec("BEGIN " + mode);
|
||||
db._inTransaction = true;
|
||||
try {
|
||||
const result = fn(...bindParameters);
|
||||
db.exec("COMMIT");
|
||||
db._inTransaction = false;
|
||||
return result;
|
||||
} catch (err) {
|
||||
db.exec("ROLLBACK");
|
||||
db._inTransaction = false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ export class Connection {
|
||||
private session: Session;
|
||||
private isOpen: boolean = true;
|
||||
private defaultSafeIntegerMode: boolean = false;
|
||||
private _inTransaction: boolean = false;
|
||||
|
||||
constructor(config: Config) {
|
||||
if (!config.url) {
|
||||
@@ -24,6 +25,19 @@ export class Connection {
|
||||
}
|
||||
this.config = config;
|
||||
this.session = new Session(config);
|
||||
|
||||
// Define inTransaction property
|
||||
Object.defineProperty(this, 'inTransaction', {
|
||||
get: () => this._inTransaction,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the database is currently in a transaction.
|
||||
*/
|
||||
get inTransaction(): boolean {
|
||||
return this._inTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +157,63 @@ export class Connection {
|
||||
this.defaultSafeIntegerMode = toggle === false ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that executes the given function in a transaction.
|
||||
*
|
||||
* @param fn - The function to wrap in a transaction
|
||||
* @returns A function that will execute fn within a transaction
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const insert = await client.prepare("INSERT INTO users (name) VALUES (?)");
|
||||
* const insertMany = client.transaction((users) => {
|
||||
* for (const user of users) {
|
||||
* insert.run([user]);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* await insertMany(['Alice', 'Bob', 'Charlie']);
|
||||
* ```
|
||||
*/
|
||||
transaction(fn: (...args: any[]) => any): any {
|
||||
if (typeof fn !== "function") {
|
||||
throw new TypeError("Expected first argument to be a function");
|
||||
}
|
||||
|
||||
const db = this;
|
||||
const wrapTxn = (mode: string) => {
|
||||
return async (...bindParameters: any[]) => {
|
||||
await db.exec("BEGIN " + mode);
|
||||
db._inTransaction = true;
|
||||
try {
|
||||
const result = await fn(...bindParameters);
|
||||
await db.exec("COMMIT");
|
||||
db._inTransaction = false;
|
||||
return result;
|
||||
} catch (err) {
|
||||
await db.exec("ROLLBACK");
|
||||
db._inTransaction = false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const properties = {
|
||||
default: { value: wrapTxn("") },
|
||||
deferred: { value: wrapTxn("DEFERRED") },
|
||||
immediate: { value: wrapTxn("IMMEDIATE") },
|
||||
exclusive: { value: wrapTxn("EXCLUSIVE") },
|
||||
database: { value: this, enumerable: true },
|
||||
};
|
||||
|
||||
Object.defineProperties(properties.default.value, properties);
|
||||
Object.defineProperties(properties.deferred.value, properties);
|
||||
Object.defineProperties(properties.immediate.value, properties);
|
||||
Object.defineProperties(properties.exclusive.value, properties);
|
||||
|
||||
return properties.default.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection.
|
||||
*
|
||||
|
||||
@@ -145,16 +145,16 @@ test.serial("Database.pragma() after close()", async (t) => {
|
||||
// Database.transaction()
|
||||
// ==========================================================================
|
||||
|
||||
test.skip("Database.transaction()", async (t) => {
|
||||
test.serial("Database.transaction()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const insert = await db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
);
|
||||
|
||||
const insertMany = db.transaction((users) => {
|
||||
const insertMany = db.transaction(async (users) => {
|
||||
t.is(db.inTransaction, true);
|
||||
for (const user of users) insert.run(user);
|
||||
for (const user of users) await insert.run(user);
|
||||
});
|
||||
|
||||
t.is(db.inTransaction, false);
|
||||
@@ -166,12 +166,12 @@ test.skip("Database.transaction()", async (t) => {
|
||||
t.is(db.inTransaction, false);
|
||||
|
||||
const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
|
||||
t.is(stmt.get(3).name, "Joey");
|
||||
t.is(stmt.get(4).name, "Sally");
|
||||
t.is(stmt.get(5).name, "Junior");
|
||||
t.is((await stmt.get(3)).name, "Joey");
|
||||
t.is((await stmt.get(4)).name, "Sally");
|
||||
t.is((await stmt.get(5)).name, "Junior");
|
||||
});
|
||||
|
||||
test.skip("Database.transaction().immediate()", async (t) => {
|
||||
test.serial("Database.transaction().immediate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const insert = await db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
|
||||
@@ -138,7 +138,7 @@ test.serial("Database.pragma() after close()", async (t) => {
|
||||
// Database.transaction()
|
||||
// ==========================================================================
|
||||
|
||||
test.skip("Database.transaction()", async (t) => {
|
||||
test.serial("Database.transaction()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const insert = db.prepare(
|
||||
@@ -164,7 +164,7 @@ test.skip("Database.transaction()", async (t) => {
|
||||
t.is(stmt.get(5).name, "Junior");
|
||||
});
|
||||
|
||||
test.skip("Database.transaction().immediate()", async (t) => {
|
||||
test.serial("Database.transaction().immediate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
const insert = db.prepare(
|
||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||
|
||||
Reference in New Issue
Block a user