uow

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 11 Imported by: 0

README

uow

uow is a framework-agnostic Unit of Work and transaction manager for Go.

It provides:

  • immutable execution-scoped binding resolution
  • explicit and ambient transaction execution
  • strict or emulated nested transactions
  • tenant-aware client selection
  • rollback-only semantics
  • native net/http and Fiber v2 integration packages
  • interceptor and hook-based observability

The package is designed for service code that should depend on a small, stable UnitOfWork contract while leaving concrete adapter behavior at the edge of the application.

Why This Exists

Transactional code often drifts into framework-specific middleware, adapter types leaking into application services, or inconsistent nested semantics. uow centralizes those rules in a transport-neutral package:

  • owners resolve one binding per execution
  • repositories fetch the current backend handle through CurrentHandle()
  • explicit and ambient execution use the same resolver and transaction model
  • tenant and override precedence remain deterministic under test

Installation

go get github.com/pakasa-io/uow

Quick Start

The module ships first-party adapters for:

  • database/sql in github.com/pakasa-io/uow/adapters/sql
  • GORM in github.com/pakasa-io/uow/adapters/gorm

It also ships first-party framework integration packages for:

  • net/http in github.com/pakasa-io/uow/framework/http
  • Fiber v2 in github.com/pakasa-io/uow/framework/fiber
package main

import (
	"context"
	"database/sql"
	"fmt"

	"github.com/pakasa-io/uow"
	sqladapter "github.com/pakasa-io/uow/adapters/sql"
)

func main() {
	db, err := sql.Open("driver-name", "dsn")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	registry := uow.NewRegistry()
	registry.MustRegister(uow.Registration{
		Adapter:    sqladapter.New("sql"),
		Client:     db,
		ClientName: "primary",
		Default:    true,
	})

	manager, err := uow.NewManager(registry, uow.DefaultConfig(), uow.ManagerOptions{})
	if err != nil {
		panic(err)
	}

	err = manager.InTx(context.Background(), uow.RootTx(
		uow.WithLabel("bootstrap"),
	), func(ctx context.Context) error {
		current := sqladapter.MustCurrent(uow.MustFrom(ctx))
		fmt.Printf("%T\n", current)
		return nil
	})
	if err != nil {
		panic(err)
	}
}

ExecutionConfig and TxConfig remain available as plain structs. When you want additive construction instead of editing struct literals, use Exec(...) for ambient execution and RootTx(...) for explicit root transactions:

execCfg := uow.Exec(
	uow.WithClient("primary"),
	uow.WithTransactional(uow.TransactionalOn),
	uow.WithReadOnly(),
	uow.WithLabel("reports"),
)

txCfg, err := uow.TxConfigFromExecution(execCfg)
if err != nil {
	panic(err)
}

err = manager.InTx(context.Background(), txCfg, func(ctx context.Context) error {
	return nil
})

Examples

Runnable end-to-end examples live under examples/:

Core Concepts

Manager

Manager is the entry point for binding resolution and managed execution.

Use:

  • ResolveInfo or ResolveBinding for owner-side lookup
  • Attach to bind a default-resolved non-transactional UnitOfWork
  • Bind to create a non-transactional execution-scoped UnitOfWork
  • Run for ambient request/job/command execution
  • InTx and InNestedTx for explicit transactional execution
UnitOfWork

Application code should depend on UnitOfWork, not adapter-specific clients.

Key rules:

  • Binding() exposes metadata only
  • CurrentHandle() returns the live transactional handle when a root exists
  • CurrentHandle() returns the bound client in non-transactional flows
  • repositories should acquire the current handle at call time
Binding Resolution

Binding resolution is deterministic and mode-aware:

  • ambient resolution applies BindingOverride before ExecutionConfig
  • explicit resolution applies TxConfig before BindingOverride
  • tenant-specific registrations win over non-tenant registrations
  • tenant fallback is allowed only when tenant resolution is not required
Nested Transactions

NestedStrict:

  • requires adapter nested transaction or savepoint support
  • returns ErrNestedTxUnsupported when unavailable

NestedEmulated:

  • never requires adapter nested support
  • nested rollback marks the root rollback-only
  • nested commit is logical only

Configuration

Start with uow.DefaultConfig() and override only the fields your application needs:

cfg := uow.DefaultConfig()
cfg.TransactionMode = uow.GlobalAuto
cfg.NestedMode = uow.NestedEmulated
cfg.RequireTenantResolution = true

You can also load the serializable subset from environment variables:

cfg, err := uow.ConfigFromEnv("UOW")

Supported keys:

  • UOW_NESTED_MODE
  • UOW_TRANSACTION_MODE
  • UOW_DEFAULT_ADAPTER_NAME
  • UOW_DEFAULT_CLIENT_NAME
  • UOW_STRICT_OPTION_ENFORCEMENT
  • UOW_ALLOW_OPTION_DOWNGRADE
  • UOW_REQUIRE_TENANT_RESOLUTION

Custom finalize policies remain code-only because they are Go interfaces.

Context Propagation

Managed execution should always propagate the UnitOfWork:

u := uow.MustFrom(ctx)
handle := u.CurrentHandle()

Optional context helpers:

  • WithBindingOverride / BindingOverrideFrom
  • WithTenantID / TenantIDFromContext
  • ContextTenantPolicy

net/http Integration

The httpuow package provides:

  • httpuow.Middleware(manager, cfg) for standard middleware composition
  • httpuow.Wrap(manager, cfg, handler) for per-route wrapping
  • request-time execution, tenant, and binding override resolution
  • optional status-based rollback policies

Per-route configuration is explicit because each route can be wrapped with its own Config:

mux.Handle("/users", httpuow.Wrap(manager, httpuow.Config{
	Execution: uow.ExecutionConfig{
		Transactional: uow.TransactionalOn,
		Label:         "list-users",
	},
	ResolveTenant: func(r *http.Request) (string, error) {
		return r.Header.Get("X-Tenant-ID"), nil
	},
	RollbackOnStatus: httpuow.RollbackOn5xx,
}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	work := uow.MustFrom(r.Context())
	_ = work.CurrentHandle()
	w.WriteHeader(http.StatusOK)
})))

For broader defaults, use the same middleware at the router or subrouter level:

secured := httpuow.Middleware(manager, httpuow.Config{
	Execution: uow.ExecutionConfig{Transactional: uow.TransactionalOn},
})

Status-based rollback is transport policy only. The middleware marks the active transaction rollback-only when configured and a matching status code is observed.

Transactional net/http routes are response-buffered until finalization so a commit failure can still produce an error response. Streaming or hijacked responses should run with TransactionalOff.

Fiber v2 Integration

The fiberuow package provides:

  • fiberuow.Middleware(manager, cfg) for app/group middleware
  • fiberuow.Wrap(manager, cfg, handler) for route-specific wrapping
  • tenant and binding override resolution from *fiber.Ctx
  • optional rollback by status code and/or returned handler error

Group middleware:

api := app.Group("/api", fiberuow.Middleware(manager, fiberuow.Config{
	Execution: uow.ExecutionConfig{Transactional: uow.TransactionalOn},
	ResolveTenant: func(c *fiber.Ctx) (string, error) {
		return c.Get("X-Tenant-ID"), nil
	},
}))

Per-route override:

app.Get("/reports/:id", fiberuow.Wrap(manager, fiberuow.Config{
	Execution: uow.ExecutionConfig{
		Transactional: uow.TransactionalOn,
		Label:         "report-detail",
	},
	RollbackOnStatus: fiberuow.RollbackOn5xx,
}, func(c *fiber.Ctx) error {
	work := uow.MustFrom(c.UserContext())
	_ = work.CurrentHandle()
	return c.SendStatus(fiber.StatusOK)
}))

Fiber middleware uses c.UserContext() / c.SetUserContext(...) to bridge the transport lifecycle into the core context.Context propagation model.

First-Party database/sql Adapter

The sqladapter package expects a registered *sql.DB client and exposes:

  • sqladapter.New(name) for adapter construction
  • sqladapter.Current(uow) to obtain a database/sql query handle
  • sqladapter.MustCurrent(uow) when repository code prefers panic-on-miswire
  • sqladapter.CurrentTx(uow) for transaction-specific paths

The adapter supports:

  • root transactions
  • ReadOnly begin options
  • standard database/sql isolation levels

The adapter intentionally does not advertise:

  • nested/savepoint transactions
  • backend transaction timeout semantics

That keeps the capability contract aligned with what database/sql can guarantee portably.

First-Party GORM Adapter

The gormadapter package expects a registered *gorm.DB client and exposes:

  • gormadapter.New(name, options...) for adapter construction
  • gormadapter.Current(uow) to obtain the current *gorm.DB
  • gormadapter.MustCurrent(uow) for panic-on-miswire repository code
  • gormadapter.CurrentTx(uow) for transaction-only paths

By default the GORM adapter is conservative:

  • root transactions are supported
  • ReadOnly and isolation preferences are passed through gorm.DB.Begin
  • nested transactions are reported as unsupported

When the backing dialect supports savepoints reliably, nested strict mode can be enabled explicitly:

adapter := gormadapter.New("gorm", gormadapter.WithNestedSavepoints(true))

This keeps the default capability contract stable across databases while still allowing savepoint-backed nesting for deployments that have validated it.

Error Model

The package returns wrapped errors that work with errors.Is and errors.As.

Typical checks:

if errors.Is(err, uow.ErrRollbackOnly) { ... }
if errors.Is(err, uow.ErrNestedTxUnsupported) { ... }

var uerr *uow.UOWError
if errors.As(err, &uerr) && uerr.Kind == uow.ErrKindResolver { ... }

Thread Safety

  • Registry supports concurrent reads and serialized writes.
  • UnitOfWork state transitions are internally synchronized.
  • Nested scopes are lexical and must be finalized in LIFO order.
  • TxScope values are not designed for long-lived asynchronous use.

Development

gofmt -w *.go
go test ./...
golangci-lint run

GitHub Actions CI runs go test ./... on pushes and pull requests. A repository-local .golangci.yml is included and an optional manual lint workflow is available without making linting a required publish gate yet.

Compatibility Notes

  • The public API is intentionally small and concrete.
  • The module avoids framework and ORM dependencies in the core package.
  • No distributed transaction support is provided.

Non-Goals

  • two-phase commit / distributed transactions
  • cross-database atomicity
  • framework-specific middleware in the core package
  • automatic adapter implementations for every ORM

Documentation

Overview

Package uow provides a framework-agnostic Unit of Work and transaction manager for single-binding execution flows.

The package is designed around a small public contract:

  • owners resolve one immutable binding per execution
  • repositories and services consume UnitOfWork from context
  • explicit and ambient execution paths share the same resolver and transaction semantics
  • nested transactions are either strict or emulated, depending on Config

The core package is transport-neutral. HTTP and ORM integrations belong in separate packages built on top of Manager, Resolver, and UnitOfWork.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoAdapterRegistered indicates that the registry is empty.
	ErrNoAdapterRegistered = errors.New("uow: no adapter registered")
	// ErrAdapterNotFound indicates that a requested adapter could not be resolved.
	ErrAdapterNotFound = errors.New("uow: adapter not found")
	// ErrClientNotFound indicates that a requested client could not be resolved.
	ErrClientNotFound = errors.New("uow: client not found")
	// ErrTenantNotResolved indicates that a required tenant was not available.
	ErrTenantNotResolved = errors.New("uow: tenant not resolved")
	// ErrTenantBindingNotFound indicates that a tenant-specific binding could not
	// be resolved.
	ErrTenantBindingNotFound = errors.New("uow: tenant binding not found")
	// ErrUOWNotFound indicates that no UnitOfWork is present in context.
	ErrUOWNotFound = errors.New("uow: unit of work not found")
	// ErrTxAlreadyClosed indicates that a scope was already finalized.
	ErrTxAlreadyClosed = errors.New("uow: transaction already closed")
	// ErrNestedTxUnsupported indicates that strict nested transactions are
	// unsupported by the selected adapter.
	ErrNestedTxUnsupported = errors.New("uow: nested transaction unsupported")
	// ErrRootTxUnsupported indicates that root transactions are unsupported by the
	// selected adapter.
	ErrRootTxUnsupported = errors.New("uow: root transaction unsupported")
	// ErrBeginAborted indicates that begin was vetoed before the adapter call.
	ErrBeginAborted = errors.New("uow: begin aborted")
	// ErrBeginFailed indicates that begin failed after the adapter call path was
	// entered.
	ErrBeginFailed = errors.New("uow: begin failed")
	// ErrCommitAborted indicates that commit failed and rollback succeeded.
	ErrCommitAborted = errors.New("uow: commit aborted")
	// ErrRollbackFailed indicates that rollback itself failed.
	ErrRollbackFailed = errors.New("uow: rollback failed")
	// ErrFinalizationFailed indicates that multiple finalization failures need to
	// be preserved together.
	ErrFinalizationFailed = errors.New("uow: finalization failed")
	// ErrBindingOverrideConflict indicates conflicting explicit and context
	// selectors.
	ErrBindingOverrideConflict = errors.New("uow: binding override conflict")
	// ErrRootOwnershipViolation indicates an attempt to control a root
	// transaction outside the owning lifecycle.
	ErrRootOwnershipViolation = errors.New("uow: root ownership violation")
	// ErrNoActiveTransaction indicates that no root transaction is active.
	ErrNoActiveTransaction = errors.New("uow: no active transaction")
	// ErrRollbackOnly indicates that commit is blocked by rollback-only state.
	ErrRollbackOnly = errors.New("uow: rollback only")
	// ErrBindingImmutable indicates that a UnitOfWork binding cannot change.
	ErrBindingImmutable = errors.New("uow: binding immutable")
	// ErrMultipleRootBindingsForbidden indicates that a single execution cannot
	// open multiple root bindings.
	ErrMultipleRootBindingsForbidden = errors.New("uow: multiple root bindings forbidden")
	// ErrScopeOrderViolation indicates nested scope finalization out of order.
	ErrScopeOrderViolation = errors.New("uow: scope order violation")
	// ErrContextCancelled indicates that execution was cancelled before commit.
	ErrContextCancelled = errors.New("uow: context cancelled")
)

Functions

func TenantIDFromContext

func TenantIDFromContext(ctx context.Context) (string, bool)

TenantIDFromContext returns the tenant identity stored in context.

func With

func With(ctx context.Context, uow UnitOfWork) context.Context

With stores a UnitOfWork in context.

func WithBindingOverride

func WithBindingOverride(ctx context.Context, override BindingOverride) context.Context

WithBindingOverride stores a binding override in context.

func WithTenantID

func WithTenantID(ctx context.Context, tenantID string) context.Context

WithTenantID stores a tenant identity in context for ContextTenantPolicy.

Types

type Adapter

type Adapter interface {
	Name() string
	Capabilities() Capabilities

	Begin(ctx context.Context, client any, opts BeginOptions) (Tx, error)
	BeginNested(ctx context.Context, parent Tx, opts NestedOptions) (Tx, error)

	Commit(ctx context.Context, tx Tx) error
	Rollback(ctx context.Context, tx Tx) error

	Unwrap(tx Tx) any
}

Adapter abstracts a transactional backend.

type BeginOptions

type BeginOptions struct {
	ReadOnly       bool
	IsolationLevel IsolationLevel
	Timeout        time.Duration
	Label          string
}

BeginOptions defines root transaction begin preferences.

type BindingInfo

type BindingInfo struct {
	AdapterName string
	ClientName  string
	TenantID    string
}

BindingInfo exposes resolved execution metadata without backend objects.

type BindingOverride

type BindingOverride struct {
	AdapterName Selector
	ClientName  Selector
	TenantID    Selector
}

BindingOverride is the context-carried override surface for binding selection.

func BindingOverrideFrom

func BindingOverrideFrom(ctx context.Context) (BindingOverride, bool)

BindingOverrideFrom returns the binding override stored in context.

type BindingResolver

type BindingResolver interface {
	ResolveBinding(ctx context.Context, req ResolutionRequest) (ResolvedBinding, error)
}

BindingResolver resolves owner-facing backend bindings.

Repository and service code should continue to consume UnitOfWork instead of using ResolvedBinding directly.

type Capabilities

type Capabilities struct {
	RootTransaction   bool
	NestedTransaction bool
	Savepoints        bool
	ReadOnlyTx        bool
	IsolationLevels   bool
	Timeouts          bool
	MultiTenantAware  bool
}

Capabilities describes an adapter's transaction features.

type Config

type Config struct {
	NestedMode              NestedMode
	TransactionMode         TransactionMode
	DefaultAdapterName      string
	DefaultClientName       string
	DefaultFinalizePolicy   FinalizePolicy
	StrictOptionEnforcement bool
	AllowOptionDowngrade    bool
	RequireTenantResolution bool
}

Config controls resolver and transaction behavior.

func ConfigFromEnv

func ConfigFromEnv(prefix string) (Config, error)

ConfigFromEnv loads Config from environment variables using the provided prefix. The prefix defaults to UOW when empty.

Supported keys are:

  • <PREFIX>_NESTED_MODE
  • <PREFIX>_TRANSACTION_MODE
  • <PREFIX>_DEFAULT_ADAPTER_NAME
  • <PREFIX>_DEFAULT_CLIENT_NAME
  • <PREFIX>_STRICT_OPTION_ENFORCEMENT
  • <PREFIX>_ALLOW_OPTION_DOWNGRADE
  • <PREFIX>_REQUIRE_TENANT_RESOLUTION
  • <PREFIX>_DEFAULT_FINALIZE_POLICY (supports only "default")

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the package defaults defined by the specification.

func (Config) Validate

func (c Config) Validate() error

Validate validates the configuration after default normalization.

type ContextTenantPolicy

type ContextTenantPolicy struct{}

ContextTenantPolicy resolves the tenant identity from WithTenantID.

func (ContextTenantPolicy) ResolveTenant

func (ContextTenantPolicy) ResolveTenant(ctx context.Context) (string, error)

ResolveTenant implements TenantResolutionPolicy.

type ErrorKind

type ErrorKind int

ErrorKind categorizes package errors for programmatic inspection.

const (
	// ErrKindConfig indicates invalid configuration or input.
	ErrKindConfig ErrorKind = iota
	// ErrKindAdapter indicates backend adapter capability or lifecycle failures.
	ErrKindAdapter
	// ErrKindResolver indicates binding resolution failures.
	ErrKindResolver
	// ErrKindTransaction indicates transaction lifecycle failures.
	ErrKindTransaction
	// ErrKindState indicates invalid UnitOfWork state transitions.
	ErrKindState
	// ErrKindTenant indicates tenant resolution failures.
	ErrKindTenant
)

func (ErrorKind) String

func (k ErrorKind) String() string

type ExecOption added in v1.1.0

type ExecOption interface {
	// contains filtered or unexported methods
}

ExecOption configures ambient execution settings for Exec(...).

func WithTransactional added in v1.1.0

func WithTransactional(mode TransactionalMode) ExecOption

WithTransactional configures ambient transaction behavior for Exec(...).

type ExecutionConfig

type ExecutionConfig struct {
	AdapterName    Selector
	ClientName     Selector
	TenantID       Selector
	Transactional  TransactionalMode
	ReadOnly       bool
	IsolationLevel IsolationLevel
	Timeout        time.Duration
	Label          string
}

ExecutionConfig controls managed ambient execution.

The zero value leaves binding selection unspecified, inherits the Manager's ambient transaction mode, and requests default backend transaction options.

func Exec added in v1.1.0

func Exec(opts ...ExecOption) ExecutionConfig

Exec builds an ExecutionConfig from additive option helpers.

func (ExecutionConfig) Validate added in v1.1.0

func (c ExecutionConfig) Validate() error

Validate validates an ambient execution configuration.

type Executor

type Executor interface {
	InTx(ctx context.Context, cfg TxConfig, fn func(ctx context.Context) error) error
	InNestedTx(ctx context.Context, opts NestedOptions, fn func(ctx context.Context) error) error
}

Executor executes explicit transactional callbacks.

type FinalizeInput

type FinalizeInput struct {
	Err              error
	PanicValue       any
	ContextCancelled bool
	UOW              UnitOfWork
}

FinalizeInput is passed to a root finalization policy.

type FinalizePolicy

type FinalizePolicy interface {
	ShouldRollback(ctx context.Context, input FinalizeInput) bool
}

FinalizePolicy decides whether a managed root transaction should rollback.

Rollback-only and context cancellation remain hard rollback conditions even when a custom policy is configured.

type Hooks

type Hooks interface {
	OnBegin(ctx context.Context, meta TxMeta)
	OnCommit(ctx context.Context, meta TxMeta, err error)
	OnRollback(ctx context.Context, meta TxMeta, err error)
	OnNestedBegin(ctx context.Context, meta TxMeta)
}

Hooks observes transaction lifecycle events after the corresponding interceptor phase.

type Interceptor

type Interceptor interface {
	BeforeBegin(ctx context.Context, meta TxMeta) error
	AfterBegin(ctx context.Context, meta TxMeta, err error)
	BeforeCommit(ctx context.Context, meta TxMeta) error
	AfterCommit(ctx context.Context, meta TxMeta, err error)
	BeforeRollback(ctx context.Context, meta TxMeta) error
	AfterRollback(ctx context.Context, meta TxMeta, err error)
}

Interceptor participates in transaction lifecycle phases.

type IsolationLevel

type IsolationLevel string

IsolationLevel is a backend-agnostic transaction isolation preference.

The zero value leaves isolation unspecified.

const (
	// IsolationReadUncommitted requests read-uncommitted semantics.
	IsolationReadUncommitted IsolationLevel = "read_uncommitted"
	// IsolationReadCommitted requests read-committed semantics.
	IsolationReadCommitted IsolationLevel = "read_committed"
	// IsolationRepeatableRead requests repeatable-read semantics.
	IsolationRepeatableRead IsolationLevel = "repeatable_read"
	// IsolationSnapshot requests snapshot semantics.
	IsolationSnapshot IsolationLevel = "snapshot"
	// IsolationSerializable requests serializable semantics.
	IsolationSerializable IsolationLevel = "serializable"
)

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns binding resolution and managed execution.

func NewManager

func NewManager(registry *Registry, cfg Config, opts ManagerOptions) (*Manager, error)

NewManager constructs a Manager.

func (*Manager) Attach added in v1.1.0

func (m *Manager) Attach(ctx context.Context) (context.Context, UnitOfWork, error)

Attach resolves or reuses a default execution-scoped UnitOfWork.

func (*Manager) Bind

Bind resolves or reuses a non-transactional execution-scoped UnitOfWork.

func (*Manager) Do deprecated

func (m *Manager) Do(ctx context.Context, cfg ExecutionConfig, fn func(ctx context.Context) error) error

Deprecated: use Run.

func (*Manager) InNestedTx

func (m *Manager) InNestedTx(ctx context.Context, opts NestedOptions, fn func(ctx context.Context) error) error

InNestedTx executes fn in a nested transaction scope.

func (*Manager) InTx

func (m *Manager) InTx(ctx context.Context, cfg TxConfig, fn func(ctx context.Context) error) error

InTx executes fn in a root transaction or nested scope.

Example
adapter := newMockAdapter(Capabilities{RootTransaction: true})
registry := NewRegistry()
if err := registry.Register(defaultRegistration(adapter)); err != nil {
	panic(err)
}
manager, err := NewManager(registry, DefaultConfig(), ManagerOptions{})
if err != nil {
	panic(err)
}

err = manager.InTx(context.Background(), RootTx(
	WithLabel("example"),
), func(ctx context.Context) error {
	u := MustFrom(ctx)
	fmt.Printf("%s/%s tx=%v\n", u.Binding().AdapterName, u.Binding().ClientName, u.InTransaction())
	return nil
})
if err != nil {
	panic(err)
}
Output:
mock/primary tx=true

func (*Manager) ResolveBinding

func (m *Manager) ResolveBinding(ctx context.Context, req ResolutionRequest) (ResolvedBinding, error)

ResolveBinding resolves an owner-facing binding.

func (*Manager) ResolveInfo

func (m *Manager) ResolveInfo(ctx context.Context, req ResolutionRequest) (BindingInfo, error)

ResolveInfo resolves public binding metadata.

func (*Manager) Run added in v1.1.0

func (m *Manager) Run(ctx context.Context, cfg ExecutionConfig, fn func(ctx context.Context) error) error

Run executes a managed ambient callback with a propagated UnitOfWork.

type ManagerOptions

type ManagerOptions struct {
	TenantPolicy TenantResolutionPolicy
	Hooks        Hooks
	Interceptors []Interceptor
	Logger       *slog.Logger
}

ManagerOptions configures optional collaborators for Manager.

type NestedMode

type NestedMode int

NestedMode controls how nested transactions behave once a root transaction exists.

const (
	// NestedStrict requires adapter-level nested transaction or savepoint
	// support.
	NestedStrict NestedMode = iota
	// NestedEmulated keeps nesting at the UnitOfWork layer and turns nested
	// rollback into root rollback-only.
	NestedEmulated
)

func ParseNestedMode

func ParseNestedMode(value string) (NestedMode, error)

ParseNestedMode parses a string representation of NestedMode.

func (NestedMode) String

func (m NestedMode) String() string

String implements fmt.Stringer.

type NestedOptions

type NestedOptions struct {
	Label string
}

NestedOptions defines nested transaction begin preferences.

type Option added in v1.1.0

type Option interface {
	TxOption
	ExecOption
}

Option configures shared fields supported by both Exec(...) and RootTx(...).

func WithAdapter added in v1.1.0

func WithAdapter(name string) Option

WithAdapter selects a concrete adapter name for Exec(...) or RootTx(...).

func WithAdapterSelector added in v1.1.0

func WithAdapterSelector(selector Selector) Option

WithAdapterSelector applies an adapter selector to Exec(...) or RootTx(...).

func WithClient added in v1.1.0

func WithClient(name string) Option

WithClient selects a concrete client name for Exec(...) or RootTx(...).

func WithClientSelector added in v1.1.0

func WithClientSelector(selector Selector) Option

WithClientSelector applies a client selector to Exec(...) or RootTx(...).

func WithIsolation added in v1.1.0

func WithIsolation(level IsolationLevel) Option

WithIsolation requests a specific transaction isolation level.

func WithLabel added in v1.1.0

func WithLabel(label string) Option

WithLabel applies an execution label for observability.

func WithReadOnly added in v1.1.0

func WithReadOnly() Option

WithReadOnly requests a read-only root transaction.

func WithTenant added in v1.1.0

func WithTenant(id string) Option

WithTenant selects a concrete tenant id for Exec(...) or RootTx(...).

func WithTenantSelector added in v1.1.0

func WithTenantSelector(selector Selector) Option

WithTenantSelector applies a tenant selector to Exec(...) or RootTx(...).

func WithTimeout added in v1.1.0

func WithTimeout(timeout time.Duration) Option

WithTimeout requests a transaction timeout.

type Registration

type Registration struct {
	AdapterName string
	ClientName  string
	TenantID    string
	Adapter     Adapter
	Client      any
	Default     bool
	Tags        map[string]string
}

Registration registers an adapter/client pair, optionally scoped to a tenant.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry stores adapter/client registrations used by the resolver.

Reads are safe for concurrent use. Writes are serialized.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty Registry.

func (*Registry) MustRegister

func (r *Registry) MustRegister(reg Registration)

MustRegister registers a binding and panics on error.

func (*Registry) Register

func (r *Registry) Register(reg Registration) error

Register registers one adapter/client binding.

func (*Registry) Registrations

func (r *Registry) Registrations() []Registration

Registrations returns a snapshot of the current registrations.

type ResolutionMode

type ResolutionMode int

ResolutionMode controls selector precedence during binding resolution.

const (
	// ResolutionAmbient applies BindingOverride before an ambient request.
	ResolutionAmbient ResolutionMode = iota
	// ResolutionExplicit applies explicit selectors before BindingOverride.
	ResolutionExplicit
)

type ResolutionRequest

type ResolutionRequest struct {
	Mode        ResolutionMode
	AdapterName Selector
	ClientName  Selector
	TenantID    Selector
}

ResolutionRequest describes a binding lookup request.

type ResolvedBinding

type ResolvedBinding struct {
	BindingInfo
	Adapter Adapter
	Client  any
}

ResolvedBinding is the owner-facing resolved binding used to create a UnitOfWork.

type Resolver

type Resolver interface {
	ResolveInfo(ctx context.Context, req ResolutionRequest) (BindingInfo, error)
}

Resolver resolves public binding metadata.

type RootController

type RootController interface {
	CommitRoot(ctx context.Context) error
	RollbackRoot(ctx context.Context) error
}

RootController finalizes the active root transaction for owner-managed execution.

type Selector

type Selector struct {
	Set   bool
	Value string
}

Selector represents an optional explicit binding selection.

The zero value leaves the selector unspecified. A set selector with an empty value explicitly requests the default or non-tenant choice for that field. Use Select to lock a concrete value, DefaultSelection to request the default selection explicitly, and NoTenant as a clearer tenant-specific alias for an explicit empty selection.

func DefaultSelection added in v1.1.0

func DefaultSelection() Selector

DefaultSelection returns a Selector that explicitly chooses the default binding for adapter or client resolution.

func NoTenant added in v1.1.0

func NoTenant() Selector

NoTenant returns a Selector that explicitly opts out of tenant selection.

It is equivalent to DefaultSelection but reads more clearly when used for tenant selectors.

func Select added in v1.1.0

func Select(value string) Selector

Select returns a Selector locked to one trimmed value.

Prefer SelectAdapter, SelectClient, or SelectTenant when the target field is known — they communicate intent at the call site.

func SelectAdapter added in v1.1.0

func SelectAdapter(name string) Selector

SelectAdapter returns a Selector that explicitly chooses a named adapter.

func SelectClient added in v1.1.0

func SelectClient(name string) Selector

SelectClient returns a Selector that explicitly chooses a named client.

func SelectTenant added in v1.1.0

func SelectTenant(id string) Selector

SelectTenant returns a Selector that explicitly chooses a named tenant.

type TenantResolutionPolicy

type TenantResolutionPolicy interface {
	ResolveTenant(ctx context.Context) (string, error)
}

TenantResolutionPolicy resolves a tenant identity from context.

type TransactionMode

type TransactionMode int

TransactionMode controls whether ambient execution starts transactions automatically.

const (
	// ExplicitOnly starts a transaction only when an explicit transactional API
	// is invoked.
	ExplicitOnly TransactionMode = iota
	// GlobalAuto starts a transaction for ambient executions unless they opt out.
	GlobalAuto
)

func ParseTransactionMode

func ParseTransactionMode(value string) (TransactionMode, error)

ParseTransactionMode parses a string representation of TransactionMode.

func (TransactionMode) String

func (m TransactionMode) String() string

String implements fmt.Stringer.

type TransactionalMode

type TransactionalMode int

TransactionalMode controls transaction activation for ambient execution.

const (
	// TransactionalInherit follows the effective transaction mode.
	TransactionalInherit TransactionalMode = iota
	// TransactionalOff disables automatic root transaction creation.
	TransactionalOff
	// TransactionalOn forces automatic root transaction creation for the
	// managed ambient execution.
	TransactionalOn
)

func (TransactionalMode) String

func (m TransactionalMode) String() string

String implements fmt.Stringer.

type Tx

type Tx interface {
	Name() string
	Depth() int
	Raw() any
}

Tx is the adapter-neutral transaction handle.

type TxConfig

type TxConfig struct {
	AdapterName    Selector
	ClientName     Selector
	TenantID       Selector
	ReadOnly       bool
	IsolationLevel IsolationLevel
	Timeout        time.Duration
	Label          string
}

TxConfig controls explicit root transaction execution.

The zero value selects the default binding and starts a read-write root transaction with default backend options.

func RootTx added in v1.1.0

func RootTx(opts ...TxOption) TxConfig

RootTx builds a TxConfig from additive option helpers.

func TxConfigFromExecution added in v1.1.0

func TxConfigFromExecution(cfg ExecutionConfig) (TxConfig, error)

TxConfigFromExecution copies the shared transaction fields from an ExecutionConfig into a TxConfig after validating the ambient config.

Example
execCfg := Exec(
	WithClient("primary"),
	WithTransactional(TransactionalOn),
	WithReadOnly(),
	WithLabel("reporting"),
)

txCfg, err := TxConfigFromExecution(execCfg)
if err != nil {
	panic(err)
}

fmt.Printf("client=%s readOnly=%v label=%s\n", txCfg.ClientName.Value, txCfg.ReadOnly, txCfg.Label)
Output:
client=primary readOnly=true label=reporting

func (TxConfig) Validate added in v1.1.0

func (c TxConfig) Validate() error

Validate validates an explicit root transaction configuration.

type TxMeta

type TxMeta struct {
	TxID          string
	TraceID       string
	SpanID        string
	AdapterName   string
	ClientName    string
	TenantID      string
	Depth         int
	Label         string
	RollbackCause error
}

TxMeta describes a transaction lifecycle event.

type TxOption added in v1.1.0

type TxOption interface {
	// contains filtered or unexported methods
}

TxOption configures shared transactional settings for RootTx(...).

type TxScope

type TxScope interface {
	Tx() Tx
	Commit(ctx context.Context) error
	Rollback(ctx context.Context) error
}

TxScope is the lexical nested transaction scope handle.

type UOWError

type UOWError struct {
	Kind ErrorKind
	Err  error
}

UOWError wraps a categorized package error.

func (*UOWError) Error

func (e *UOWError) Error() string

Error implements error.

func (*UOWError) Unwrap

func (e *UOWError) Unwrap() error

Unwrap exposes the underlying error chain.

type UnitOfWork

type UnitOfWork interface {
	Binding() BindingInfo

	InTransaction() bool
	Root() (Tx, bool)
	Current() (Tx, bool)
	CurrentHandle() any

	BeginNested(ctx context.Context, opts NestedOptions) (TxScope, error)

	SetRollbackOnly(reason error) error
	IsRollbackOnly() bool
	RollbackReason() error
}

UnitOfWork is the application-facing execution-scoped transaction facade.

Implementations are safe for concurrent use within one execution, but remain execution-scoped values and should not be reused after their owning request or job has completed.

func From

func From(ctx context.Context) (UnitOfWork, bool)

From returns the UnitOfWork stored in context.

func MustFrom

func MustFrom(ctx context.Context) UnitOfWork

MustFrom returns the UnitOfWork stored in context or panics with ErrUOWNotFound.

Directories

Path Synopsis
adapters
gorm
Package gormadapter provides a first-party uow adapter for GORM.
Package gormadapter provides a first-party uow adapter for GORM.
sql
Package sqladapter provides a first-party uow adapter for database/sql.
Package sqladapter provides a first-party uow adapter for database/sql.
examples
callbacks command
gorm command
http command
framework
fiber
Package fiberuow provides native Fiber v2 integration for github.com/pakasa-io/uow.
Package fiberuow provides native Fiber v2 integration for github.com/pakasa-io/uow.
http
Package httpuow provides net/http integration for github.com/pakasa-io/uow.
Package httpuow provides net/http integration for github.com/pakasa-io/uow.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL