# SurrealTransaction

The SurrealTransaction class provides atomic transaction support for executing multiple queries.

The `SurrealTransaction` class provides transaction support for executing multiple queries atomically. When all desired queries have been executed, call [`commit()`](#commit) to apply the changes to the database, or [`cancel()`](#cancel) to discard them.

Transactions are created using the [`beginTransaction()`](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) method on a [`SurrealSession`](/docs/reference/javascript/api/core/surreal-session.md) instance.

**Extends:** [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md)

**Source:** [api/transaction.ts](https://github.com/surrealdb/surrealdb.js/blob/main/packages/sdk/src/api/transaction.ts)

## Overview

Transactions ensure that a group of operations are executed atomically - either all succeed or all fail. This is essential for maintaining data consistency when performing related operations.

```ts
const txn = await db.beginTransaction();

try {
    // All operations succeed or fail together
    await txn.create(recordId1).content(data1);
    await txn.create(recordId2).content(data2);
    await txn.commit(); // Apply all changes
} catch (error) {
    await txn.cancel(); // Discard all changes
}
```

## Constructor

The constructor is not called directly. Use [`SurrealSession.beginTransaction()`](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) to create transactions.

## Transaction methods

### `.commit()` {#commit}

Commit the transaction to the database, applying all changes made within the transaction scope.

After committing, the transaction cannot be used again.

```ts title="Method Syntax"
txn.commit()
```

#### Returns
`Promise<void>` - Resolves when the transaction is committed

#### Example
```ts
const txn = await db.beginTransaction();

await txn.create(new RecordId('users', 'alice'))
    .content({ name: 'Alice', email: 'alice@example.com' });

await txn.create(new RecordId('users', 'bob'))
    .content({ name: 'Bob', email: 'bob@example.com' });

// Commit both creates atomically
await txn.commit();
console.log('Transaction committed successfully');
```

### `.cancel()` {#cancel}

Cancel and discard all changes made in the transaction.

After canceling, the transaction cannot be used again.

```ts title="Method Syntax"
txn.cancel()
```

#### Returns
`Promise<void>` - Resolves when the transaction is cancelled

#### Example
```ts
const txn = await db.beginTransaction();

try {
    await txn.create(new RecordId('users', 'alice'))
        .content({ name: 'Alice' });
    
    // Something goes wrong
    throw new Error('Validation failed');
} catch (error) {
    // Discard all changes
    await txn.cancel();
    console.log('Transaction cancelled:', error.message);
}
```

## Inherited methods

As `SurrealTransaction` extends [`SurrealQueryable`](/docs/reference/javascript/api/core/surreal-queryable.md), it inherits all query execution methods. All queries executed on a transaction instance are part of the transaction scope:

### Query methods
- [`query()`](/docs/reference/javascript/api/core/surreal-queryable.md#query) - Execute raw SurrealQL
- [`select()`](/docs/reference/javascript/api/core/surreal-queryable.md#select) - Select records
- [`create()`](/docs/reference/javascript/api/core/surreal-queryable.md#create) - Create records
- [`insert()`](/docs/reference/javascript/api/core/surreal-queryable.md#insert) - Insert records
- [`update()`](/docs/reference/javascript/api/core/surreal-queryable.md#update) - Update records
- [`upsert()`](/docs/reference/javascript/api/core/surreal-queryable.md#upsert) - Upsert records
- [`delete()`](/docs/reference/javascript/api/core/surreal-queryable.md#delete) - Delete records
- [`relate()`](/docs/reference/javascript/api/core/surreal-queryable.md#relate) - Create graph relationships
- [`live()`](/docs/reference/javascript/api/core/surreal-queryable.md#live) - Subscribe to live queries
- [`liveOf()`](/docs/reference/javascript/api/core/surreal-queryable.md#liveof) - Subscribe to existing live queries
- [`run()`](/docs/reference/javascript/api/core/surreal-queryable.md#run) - Execute functions
- [`auth()`](/docs/reference/javascript/api/core/surreal-queryable.md#auth) - Get authenticated record user
- [`api()`](/docs/reference/javascript/api/core/surreal-queryable.md#api) - Access user-defined APIs

All of these operations are executed within the transaction context and will be committed or cancelled together.

## Complete examples

### Basic transaction

```ts
import { Surreal, RecordId } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');
await db.use({ namespace: 'main', database: 'main' });

// Start a transaction
const txn = await db.beginTransaction();

try {
    // Create a user
    const user = await txn.create(new RecordId('users', 'john'))
        .content({
            name: 'John Doe',
            email: 'john@example.com',
            balance: 1000
        });
    
    // Create a purchase
    const purchase = await txn.create(new RecordId('purchases', 'purchase1'))
        .content({
            user: new RecordId('users', 'john'),
            amount: 100,
            item: 'Widget'
        });
    
    // Update user balance
    await txn.update(new RecordId('users', 'john'))
        .merge({ balance: 900 });
    
    // Commit all changes atomically
    await txn.commit();
    console.log('Purchase completed successfully');
} catch (error) {
    // If anything fails, cancel the transaction
    await txn.cancel();
    console.error('Purchase failed:', error);
}
```

### Money transfer transaction

```ts
async function transferMoney(
    db: Surreal,
    fromUser: string,
    toUser: string,
    amount: number
) {
    const txn = await db.beginTransaction();
    
    try {
        // Get current balances
        const from = await txn.select(new RecordId('users', fromUser));
        const to = await txn.select(new RecordId('users', toUser));
        
        if (!from || !to) {
            throw new Error('User not found');
        }
        
        if (from.balance < amount) {
            throw new Error('Insufficient funds');
        }
        
        // Update both balances
        await txn.update(new RecordId('users', fromUser))
            .merge({ balance: from.balance - amount });
        
        await txn.update(new RecordId('users', toUser))
            .merge({ balance: to.balance + amount });
        
        // Create transaction record
        await txn.create(new RecordId('transactions', crypto.randomUUID()))
            .content({
                from: new RecordId('users', fromUser),
                to: new RecordId('users', toUser),
                amount,
                timestamp: new Date()
            });
        
        // Commit all changes
        await txn.commit();
        console.log(`Transferred ${amount} from ${fromUser} to ${toUser}`);
        return true;
    } catch (error) {
        await txn.cancel();
        console.error('Transfer failed:', error);
        return false;
    }
}

// Use the function
await transferMoney(db, 'alice', 'bob', 50);
```

### Complex transaction with graph relationships

```ts
async function createUserWithFollows(
    db: Surreal,
    userData: { name: string; email: string },
    followUserIds: string[]
) {
    const txn = await db.beginTransaction();
    
    try {
        // Create the user
        const userId = crypto.randomUUID();
        const user = await txn.create(new RecordId('users', userId))
            .content(userData);
        
        // Create follow relationships
        for (const followId of followUserIds) {
            await txn.relate(
                new RecordId('users', userId),
                new Table('follows'),
                new RecordId('users', followId),
                { followedAt: new Date() }
            );
        }
        
        // Create initial activity log
        await txn.create(new RecordId('activity', crypto.randomUUID()))
            .content({
                user: new RecordId('users', userId),
                action: 'user_created',
                timestamp: new Date()
            });
        
        // Commit everything
        await txn.commit();
        console.log('User and relationships created successfully');
        return user;
    } catch (error) {
        await txn.cancel();
        console.error('Failed to create user:', error);
        throw error;
    }
}
```

### Transaction with error handling

```ts
async function atomicBulkOperation(db: Surreal, records: any[]) {
    const txn = await db.beginTransaction();
    const results: any[] = [];
    const errors: any[] = [];
    
    try {
        for (const record of records) {
            try {
                const result = await txn.create(new Table('items'))
                    .content(record);
                results.push(result);
            } catch (error) {
                errors.push({ record, error });
            }
        }
        
        // Only commit if all succeeded
        if (errors.length === 0) {
            await txn.commit();
            console.log(`Successfully created ${results.length} records`);
            return { success: true, results };
        } else {
            await txn.cancel();
            console.log(`Failed with ${errors.length} errors, rolled back`);
            return { success: false, errors };
        }
    } catch (error) {
        await txn.cancel();
        console.error('Transaction failed:', error);
        return { success: false, errors: [error] };
    }
}
```

## Best practices

### 1. Always handle errors

Always use try-catch blocks to ensure transactions are properly cancelled on errors:

```ts
const txn = await db.beginTransaction();
try {
    // Operations
    await txn.commit();
} catch (error) {
    await txn.cancel(); // Important!
    throw error;
}
```

### 2. Keep transactions short

Execute transactions quickly to avoid locking resources:

```ts
// Good: Short transaction
const txn = await db.beginTransaction();
await txn.update(recordId).merge(data);
await txn.commit();

// Avoid: Long-running operations in transactions
const txn = await db.beginTransaction();
await expensiveExternalApiCall(); // Bad!
await txn.update(recordId).merge(data);
await txn.commit();
```

### 3. Don't reuse transactions

Once a transaction is committed or cancelled, create a new one for subsequent operations:

```ts
const txn1 = await db.beginTransaction();
await txn1.create(record1);
await txn1.commit();

// Create a new transaction for next operation
const txn2 = await db.beginTransaction();
await txn2.create(record2);
await txn2.commit();
```

### 4. Validate before transaction

Perform validation before starting a transaction when possible:

```ts
// Validate first
if (!isValidEmail(email)) {
    throw new Error('Invalid email');
}

// Then transact
const txn = await db.beginTransaction();
// ... operations
await txn.commit();
```

## See also

- [SurrealSession.beginTransaction()](/docs/reference/javascript/api/core/surreal-session.md#begintransaction) - Creating transactions
- [SurrealQueryable](/docs/reference/javascript/api/core/surreal-queryable.md) - Available query methods
- [Query builders](/docs/reference/javascript/api/queries/) - Query builder classes
