# Transactions

A KV transaction reads keys, decides, and writes, all or nothing: selling the
last item of stock and recording the order together, so that two buyers can
never both take it. Writes that need no reads need no transaction: separate
calls already share commits.

```ts
const stock = store.bucket<number>('stock')
const orders = store.bucket<Order>('orders')

const placed = await store.tx(async tx => {
	const left = (await tx.with(stock).get(sku)) ?? 0
	if (left < 1) return false
	await tx.with(stock).set(sku, left - 1)
	await tx.with(orders).set(orderId, order)
	return true
})
```

```python
stock = store.bucket("stock", int)
orders = store.bucket("orders", Order)


async def sell(tx) -> bool:
    left = await tx.with_(stock).get(sku) or 0
    if left < 1:
        return False
    await tx.with_(stock).set(sku, left - 1)
    await tx.with_(orders).set(order_id, order)
    return True


placed = await store.tx(sell)
```

```go
stock, err := kv.OpenBucket[int64](store, "stock")
orders, err := kv.OpenBucket[Order](store, "orders")

err = store.Tx(ctx, func(tx *tinystore.Tx) error {
	left, _, err := stock.In(tx).Get(ctx, sku)
	if err != nil {
		return err
	}
	if left < 1 {
		return ErrSoldOut // rolls back
	}
	if err := stock.In(tx).Set(ctx, sku, left-1); err != nil {
		return err
	}
	return orders.In(tx).Set(ctx, orderID, order)
})
```

```rust
let stock = store.bucket::<i64>("stock").open()?;
let orders = store.bucket::<Order>("orders").open()?;

let placed = store.tx(|tx| -> Result<bool, ShopError> {
    let left = tx.with(&stock).get(&sku)?.unwrap_or(0);
    if left < 1 {
        return Ok(false);
    }
    tx.with(&stock).set(&sku, &(left - 1))?;
    tx.with(&orders).set(&order_id, &order)?;
    Ok(true)
})?;
```

## How it behaves

- `tx.with(handle)` takes a bucket or counters into the transaction (`with_`
  in Python, `handle.In(tx)` in Go). Every call inside goes through it.
- The function's return commits; a throw, an error or a panic rolls back
  (contract DUR-02).
- A call on the handle itself, `stock.get(sku)` instead of
  `tx.with(stock).get(sku)`, is `invalid` inside the function: it would read
  the file without the transaction's own writes.
- `store.tx` is a transaction of one file. Buckets live in `kv.db` and queues
  in `jobs.db`, so a handle of the other file is `invalid`. To commit keys and
  jobs together, open both from a database, `db.bucket` and `db.queue`, and
  use `db.tx`.

## Across a connection

In Rust a transaction holds `kv.db`'s writer while it reads and decides. Over
the pipe or the network, a transaction never holds the writer while your
program awaits: its reads go at once, its writes are sent together when the
function returns, with a check that nothing it read has changed. If something
did, the function runs again, five times at most before `conflict`. So keep
the function free of side effects outside the store: it may run more than
once.

## See also

- [KV](https://tinyshed.org/tinystore/docs/guides/kv): the engine's overview.
- [Versions](https://tinyshed.org/tinystore/docs/guides/kv/versions): one key's read-modify-write without a
  transaction.
- [Two engines in one file](https://tinyshed.org/tinystore/docs/handbook/start/directory#two-engines-in-one-file):
  keys and jobs that commit with rows.
