LogoRobo.js

Transactions

ACID-like transactions with serial and optimistic concurrency modes.

Transactions wrap multiple Flashcore operations in an atomic unit — either all operations succeed, or none do.

Basic Usage

import { Flashcore } from 'robo.js/flashcore'

const result = await Flashcore.$.transaction(async (ctx) => {
	// Read within the transaction
	const user = await ctx.read('user:123')

	// Stage writes (applied on commit)
	ctx.set('user:123', { ...user, xp: user.xp + 100 })
	ctx.set('audit:latest', { action: 'xp-grant', userId: '123' })

	return { granted: 100 }
})

console.log(result) // { granted: 100 }
import { Flashcore } from 'robo.js/flashcore'

const result = await Flashcore.$.transaction(async (ctx) => {
	// Read within the transaction
	const user = await ctx.read('user:123')

	// Stage writes (applied on commit)
	ctx.set('user:123', { ...user, xp: user.xp + 100 })
	ctx.set('audit:latest', { action: 'xp-grant', userId: '123' })

	return { granted: 100 }
})

console.log(result) // { granted: 100 }

The transaction context provides:

  • ctx.read(key) — Read a value (tracks version for conflict detection)
  • ctx.set(key, value) — Stage a write
  • ctx.delete(key) — Stage a delete
  • ctx.mode — The resolved transaction mode

Transaction Modes

ModeDescriptionBest For
serialQueue-based — transactions execute one at a timeHigh-contention keys
optimisticVersion-based — retries on conflictLow-contention, high-throughput
autoAdapter decides the best modeDefault
// Force serial mode
await Flashcore.$.transaction(async (ctx) => {
	// ...
}, { mode: 'serial' })

// Force optimistic mode
await Flashcore.$.transaction(async (ctx) => {
	// ...
}, { mode: 'optimistic' })
// Force serial mode
await Flashcore.$.transaction(async (ctx) => {
	// ...
}, { mode: 'serial' })

// Force optimistic mode
await Flashcore.$.transaction(async (ctx) => {
	// ...
}, { mode: 'optimistic' })

Options

await Flashcore.$.transaction(async (ctx) => {
	// ...
}, {
	mode: 'auto',        // Transaction mode
	maxRetries: 3,       // Max retries on conflict (optimistic mode)
	timeout: 5000        // Timeout in ms
})
await Flashcore.$.transaction(async (ctx) => {
	// ...
}, {
	mode: 'auto',        // Transaction mode
	maxRetries: 3,       // Max retries on conflict (optimistic mode)
	timeout: 5000        // Timeout in ms
})

Conflict Handling

In optimistic mode, Flashcore tracks versions of read values. On commit, if any read value was modified by another operation, a TransactionConflictError is thrown and the transaction is retried (up to maxRetries):

import { TransactionConflictError } from 'robo.js/flashcore'

try {
	await Flashcore.$.transaction(async (ctx) => {
		const balance = await ctx.read('balance:123')
		await ctx.write('balance:123', balance - 100)
	}, { mode: 'optimistic', maxRetries: 5 })
} catch (error) {
	if (error instanceof TransactionConflictError) {
		console.log('Transaction failed after max retries')
	}
}
import { TransactionConflictError } from 'robo.js/flashcore'

try {
	await Flashcore.$.transaction(async (ctx) => {
		const balance = await ctx.read('balance:123')
		await ctx.write('balance:123', balance - 100)
	}, { mode: 'optimistic', maxRetries: 5 })
} catch (error) {
	if (error instanceof TransactionConflictError) {
		console.log('Transaction failed after max retries')
	}
}

Requirements

Transaction support depends on adapter capabilities:

  • Serial mode — Requires transaction or atomicBatch capability for atomic commit.
  • Optimistic mode — Requires transaction or atomicBatch capability for atomic commit.
  • Full ACID — Requires adapter with native transaction or atomicBatch support.

For transaction support, use a database-backed adapter like PostgreSQL via Keyv that provides native transaction() or atomicBatch() capabilities.

On this page