Skip to content

Transactions ​

Arkorm exposes transactions through Model.transaction(...). The callback runs inside a transaction scope, and every Arkorm query executed inside that callback automatically resolves delegates from the active transaction client.

That means you can keep writing normal model and query-builder code without threading a transaction object through every method.

Basic usage ​

ts
import { User } from './models/User'

await User.transaction(async () => {
  await User.query().create({
    name: 'Mia',
    email: '[email protected]',
    isActive: 1,
  })

  await User.query().where({ email: '[email protected]' }).updateFrom({ isActive: 1 })
})

If the callback completes successfully, Arkorm commits the transaction. If the callback throws, Arkorm rolls the transaction back.

Rollback behavior ​

ts
await User.transaction(async () => {
  await User.query().create({
    name: 'Rollback Example',
    email: '[email protected]',
    isActive: 1,
  })

  throw new Error('abort transaction')
})

The insert above is discarded because the callback throws before the transaction completes.

Returning values from a transaction ​

Model.transaction() returns whatever your callback returns.

ts
const createdUser = await User.transaction(async () => {
  const user = await User.query().create({
    name: 'Nina',
    email: '[email protected]',
    isActive: 1,
  })

  return user
})

Nested transactions ​

Nested Model.transaction() calls reuse the currently active transaction instead of opening a second one.

ts
await User.transaction(async () => {
  await User.query().create({
    name: 'Outer User',
    email: '[email protected]',
    isActive: 1,
  })

  await User.transaction(async () => {
    await User.query().create({
      name: 'Inner User',
      email: '[email protected]',
      isActive: 1,
    })
  })
})

If the outer transaction rolls back, the inner work rolls back with it.

Using the transaction client directly ​

The callback also receives the active Prisma transaction client. Use it when you need to mix Arkorm queries with direct delegate calls.

ts
await User.transaction(async (tx) => {
  await User.query().create({
    name: 'Mixed Flow',
    email: '[email protected]',
    isActive: 1,
  })

  await (tx as any).userProfile.create({
    data: { userId: 1 },
  })
})

In most Arkorm flows you do not need the tx argument because Model.query() already uses the active transaction client automatically.

Transaction options ​

Arkorm forwards the optional second argument to the underlying Prisma interactive transaction call.

ts
await User.transaction(
  async () => {
    await User.query().whereKey('id', 1).updateFrom({ isActive: 1 })
  },
  {
    timeout: 10_000,
    maxWait: 5_000,
    isolationLevel: 'Serializable',
  },
)

Use this when you need to control transaction timeouts or isolation semantics.

Requirements ​

Transaction support depends on Arkorm having access to a runtime Prisma client. Use defineConfig({ prisma }) or configureArkormRuntime(...) during application boot so Arkorm can start and scope transactions correctly.

ts
import { configureArkormRuntime } from 'arkormx'

configureArkormRuntime(() => prisma)

If your current adapter does not expose transaction support, Arkorm throws an unsupported-adapter error when Model.transaction() is called.