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;
|
db: NativeDB;
|
||||||
memory: boolean;
|
memory: boolean;
|
||||||
open: 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.
|
* 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, {
|
Object.defineProperties(this, {
|
||||||
inTransaction: {
|
inTransaction: {
|
||||||
get() {
|
get: () => this._inTransaction,
|
||||||
throw new Error("not implemented");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
get() {
|
get() {
|
||||||
@@ -117,12 +116,15 @@ class Database {
|
|||||||
const wrapTxn = (mode) => {
|
const wrapTxn = (mode) => {
|
||||||
return (...bindParameters) => {
|
return (...bindParameters) => {
|
||||||
db.exec("BEGIN " + mode);
|
db.exec("BEGIN " + mode);
|
||||||
|
db._inTransaction = true;
|
||||||
try {
|
try {
|
||||||
const result = fn(...bindParameters);
|
const result = fn(...bindParameters);
|
||||||
db.exec("COMMIT");
|
db.exec("COMMIT");
|
||||||
|
db._inTransaction = false;
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
|
db._inTransaction = false;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class Database {
|
|||||||
db: NativeDB;
|
db: NativeDB;
|
||||||
memory: boolean;
|
memory: boolean;
|
||||||
open: 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.
|
* 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;
|
this.memory = db.memory;
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
inTransaction: {
|
inTransaction: {
|
||||||
get() {
|
get: () => this._inTransaction,
|
||||||
throw new Error("not implemented");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
get() {
|
get() {
|
||||||
@@ -121,12 +120,15 @@ class Database {
|
|||||||
const wrapTxn = (mode) => {
|
const wrapTxn = (mode) => {
|
||||||
return (...bindParameters) => {
|
return (...bindParameters) => {
|
||||||
db.exec("BEGIN " + mode);
|
db.exec("BEGIN " + mode);
|
||||||
|
db._inTransaction = true;
|
||||||
try {
|
try {
|
||||||
const result = fn(...bindParameters);
|
const result = fn(...bindParameters);
|
||||||
db.exec("COMMIT");
|
db.exec("COMMIT");
|
||||||
|
db._inTransaction = false;
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
db.exec("ROLLBACK");
|
db.exec("ROLLBACK");
|
||||||
|
db._inTransaction = false;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class Connection {
|
|||||||
private session: Session;
|
private session: Session;
|
||||||
private isOpen: boolean = true;
|
private isOpen: boolean = true;
|
||||||
private defaultSafeIntegerMode: boolean = false;
|
private defaultSafeIntegerMode: boolean = false;
|
||||||
|
private _inTransaction: boolean = false;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
if (!config.url) {
|
if (!config.url) {
|
||||||
@@ -24,6 +25,19 @@ export class Connection {
|
|||||||
}
|
}
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.session = new Session(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;
|
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.
|
* Close the connection.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -145,16 +145,16 @@ test.serial("Database.pragma() after close()", async (t) => {
|
|||||||
// Database.transaction()
|
// Database.transaction()
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
test.skip("Database.transaction()", async (t) => {
|
test.serial("Database.transaction()", async (t) => {
|
||||||
const db = t.context.db;
|
const db = t.context.db;
|
||||||
|
|
||||||
const insert = await db.prepare(
|
const insert = await db.prepare(
|
||||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||||
);
|
);
|
||||||
|
|
||||||
const insertMany = db.transaction((users) => {
|
const insertMany = db.transaction(async (users) => {
|
||||||
t.is(db.inTransaction, true);
|
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);
|
t.is(db.inTransaction, false);
|
||||||
@@ -166,12 +166,12 @@ test.skip("Database.transaction()", async (t) => {
|
|||||||
t.is(db.inTransaction, false);
|
t.is(db.inTransaction, false);
|
||||||
|
|
||||||
const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
|
const stmt = await db.prepare("SELECT * FROM users WHERE id = ?");
|
||||||
t.is(stmt.get(3).name, "Joey");
|
t.is((await stmt.get(3)).name, "Joey");
|
||||||
t.is(stmt.get(4).name, "Sally");
|
t.is((await stmt.get(4)).name, "Sally");
|
||||||
t.is(stmt.get(5).name, "Junior");
|
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 db = t.context.db;
|
||||||
const insert = await db.prepare(
|
const insert = await db.prepare(
|
||||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ test.serial("Database.pragma() after close()", async (t) => {
|
|||||||
// Database.transaction()
|
// Database.transaction()
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
test.skip("Database.transaction()", async (t) => {
|
test.serial("Database.transaction()", async (t) => {
|
||||||
const db = t.context.db;
|
const db = t.context.db;
|
||||||
|
|
||||||
const insert = db.prepare(
|
const insert = db.prepare(
|
||||||
@@ -164,7 +164,7 @@ test.skip("Database.transaction()", async (t) => {
|
|||||||
t.is(stmt.get(5).name, "Junior");
|
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 db = t.context.db;
|
||||||
const insert = db.prepare(
|
const insert = db.prepare(
|
||||||
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
"INSERT INTO users(name, email) VALUES (:name, :email)"
|
||||||
|
|||||||
Reference in New Issue
Block a user