drel

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: MIT Imports: 33 Imported by: 0

README

Drel

Code-generation-based Go ORM for Postgres and SQLite/LibSQL (Turso). Type-safe queries, snapshot-based change tracking, zero runtime reflection, EF Core-level developer experience.

Installation

go install github.com/alternayte/drel/cmd/drel@latest

Install

go get github.com/alternayte/drel

Quick Start

1. Define a model
package models

import "github.com/alternayte/drel"

type Task struct {
    drel.Model[int]
    Title    string `db:"title"`
    Done     bool   `db:"done"`
    Priority int    `db:"priority"`
}

func NewTask(title string, priority int) *Task {
    return &Task{Title: title, Priority: priority}
}

func (t *Task) MarkDone() { t.Done = true }
2. Generate code
drel generate

This produces type-safe query builders, scan functions, snapshot/diff helpers, and a DB struct that aggregates all discovered models.

3. Use it
// Open the generated DB
database, err := db.Open(dsn)

// Insert inside a transaction that travels through the context
err = database.WithTx(ctx, func(ctx context.Context) error {
    database.Tx(ctx).Tasks.Add(models.NewTask("Build ORM", 1))
    return nil
})

// Query with generated type-safe columns (read-only, untracked)
tasks, err := database.Tasks.
    Where(models.Tasks.Done.IsFalse()).
    OrderBy(models.Tasks.Priority.Asc()).
    All(ctx)

// Update with change tracking (only modified columns are UPDATEd)
err = database.WithTx(ctx, func(ctx context.Context) error {
    task, err := database.Tx(ctx).Tasks.Find(ctx, 1) // tracked
    if err != nil {
        return err
    }
    task.MarkDone()
    return nil
})

// A nested WithTx call joins the same transaction through a savepoint.
// Reach the transaction anywhere with drel.MustFromContext(ctx).

// Or an explicit multi-statement transaction:
err = database.Transaction(ctx, func(tx *drel.Tx) error {
    repo := drel.NewTxRepository(tx, models.TaskMeta)
    repo.Add(models.NewTask("ship it", 2))
    return tx.SaveChanges(ctx)
})

Features

  • No reflection -- all scanning, diffing, and query building use generated code.

  • Snapshot-based change tracking -- only modified columns appear in UPDATE statements.

  • Type-safe query builder -- compile-time checked column predicates and ordering (Eq, In, Between, ILike, Raw), range operators (GT/GTE/LT/LTE, plus Before/After) on time.Time, uuid.UUID, and value-object columns, conditional WhereIf, and valid SQL for empty In/And/Or (no IN () corruption).

  • Value objects -- single-column VOs via the sql.Scanner / driver.Valuer contract (the SQL column type is inferred from the underlying primitive), and multi-column VOs via drel.MultiColumnMapper (e.g. Money → amount + currency), both mapped end-to-end through codegen including change-tracking diffs.

  • JSON & array columns -- slice, map, and struct fields map to jsonb (Postgres) / TEXT (SQLite) through drel.JSON[T], with structural change-detection in diffs; native Postgres arrays via a type= tag override.

  • Transactions -- explicit transaction API with configurable isolation, read-only transactions (WithReadOnly), advisory locks (Tx.AdvisoryLock / TryAdvisoryLock; a SQLite no-op), automatic retry on serialization failures (TransactionWithRetry / WithRetry), and automatic flush on commit. Projections, includes, bulk operations, and batching all run on the transaction's own connection inside an explicit Tx.

  • Soft delete, versioning, audit -- embed drel.SoftDelete, drel.Versioned, or drel.Audit for automatic column management.

  • Primary keys -- integer auto-increment by default, or application-assigned UUIDv7 via drel.Model[uuid.UUID] (generated and stamped at Add(), so the id is valid before any flush; time-ordered for index locality). Pluggable per-model via SetKeyStrategy / SetKeyGenerator.

  • Relationships -- generated RelationInfo and IncludeSpec for has-many, has-one, belongs-to, and many-to-many eager loading with cross-package support. Filter-aware includes respect soft-delete on related models, with Unscoped() opt-out.

  • Bulk operations -- BulkInsert, BulkUpdate, BulkDelete, BulkUpsert with batching, a Postgres COPY fast-path, ON CONFLICT DO NOTHING, and full-table guards (an unfiltered BulkUpdate/BulkDelete errors unless you opt in with AllRows()). App-assigned keys, audit, and version columns are honored in bulk paths.

  • Domain events & outbox -- record events on entities, dispatch them after commit, and optionally persist them to a transactional outbox table via Engine.UseOutbox. drel.NewRelay publishes the table: it claims a batch under a lease, so more than one replica can poll one table, and it keeps the messages of one PartitionKey in order. A failed message retries, and it moves to the dead-letter state after the attempt limit.

  • Feature slices -- a modules: block in drel.yaml gives each slice its own models and its own migrations, embedded with //go:embed and merged by Engine.ApplyMigrationsFS in version order. The generated DB gains db.Modules.<Slice> and db.Tx(ctx).Modules.<Slice>, so a slice reaches only its own repositories while the transaction stays shared.

  • Test harness -- dreltest.WithRollback runs one test inside one transaction and rolls it back. The transaction travels in the context, so the code under test joins it. On Postgres the tests can run in parallel against one database.

  • Event store -- drel/es appends events to a stream inside the caller's transaction, so the events and the aggregate commit together. The pair of stream and version gives optimistic concurrency. ReadAll walks the log behind a transaction watermark, so a projection never skips an event that a slow transaction committed late. es.NewCheckpoints saves a projection's position in the same transaction as the read model write, so a failed handler never advances the checkpoint.

  • Inbox -- drel.NewInbox suppresses a duplicate delivery. Claim writes the dedupe row in the same transaction as the application write, so the two commit together. The key is the pair of message ID and handler name.

  • Pagination -- offset (PageOffset) and keyset/cursor (Page) paging with a deterministic primary-key tiebreaker.

  • Projections & aggregations -- Select, Aggregate, GroupBy into arbitrary DTOs.

  • Nested & filtered includes -- Include(Users.Posts.Then(Posts.Tags)), with Where/OrderBy/Limit per relationship; split-query loading avoids cartesian products.

  • Change-tracking depth -- tracked queries by default, AsNoTracking, Attach/Detach, and nested Savepoints.

  • Migrations -- dialect-aware schema generation and a structured snapshot diff (drel migrate new) that emits add/drop/alter for tables, columns, types, nullability, and indexes; up/down/status/lint for both dialects. Declare indexes/checks with db: tag options. SQLite emits a real table rebuild for a column type, nullability, default, or CHECK change, instead of a -- WARNING comment; several changes to one table produce one rebuild.

    Declare a column or table rename with renamed_from= -- drel never guesses a rename, because a wrong guess destroys the old column's data:

    type Order struct {
        drel.Model[int] `db:"table=orders,renamed_from=purchases"`
    
        EmailAddress string `db:"email_address,renamed_from=email"`
    }
    

    drel migrate new reads the marker once, emits ALTER TABLE purchases RENAME TO orders and ALTER TABLE orders RENAME COLUMN email TO email_address, and after that the marker is no longer needed -- remove it once its migration is generated. An ambiguous marker (the old name is still in use, or two columns claim the same old name) fails migration generation rather than guess.

  • Read replicas -- WithReadReplica round-robins reads; writes and transactions use the primary; Primary() forces read-your-writes.

  • Query batching -- NewBatch + BatchAll/BatchFirst/BatchCount pipeline queries over pgx (sequential fallback elsewhere).

  • Observability -- structured slog query logging, slow-query and dev-mode diagnostics (N+1, unbounded queries, missing-index hints), and an OpenTelemetry-adaptable Tracer.

  • CLI -- drel init, generate (with --watch and //go:generate support), migrate, seed; dialect validation and atomic code generation that fails loudly on duplicate model names, unresolved relations, or unsupported field types.

  • Health & timeouts -- Engine.Ping, HealthCheck, and Stats, plus a configurable per-query timeout (WithQueryTimeout) and a PgBouncer-compatible simple-exec mode.

  • Typed errors -- errors.Is(err, drel.ErrUniqueViolation) (and FK / not-null / check / serialization-failure / not-found / concurrency-conflict), classified uniformly across Postgres, SQLite, and LibSQL; the original driver error stays reachable via errors.As.

  • Connection pool control -- WithMaxConns, WithConnMaxLifetime, WithConnMaxIdleTime.

  • Raw SQL escape hatches -- Engine.Exec, Engine.Query, Engine.QueryRow, RawQuery[T], and Tx.Exec, Tx.QueryRow for anything the ORM does not cover.

  • Composite and named primary keys -- pass a comparable struct to drel.Model[K] for a multi-column key, one column per exported field, named by its own db tag; or set the first position of the db tag on the embedded drel.Model field to name a single scalar key column, for example drel.Model[string] \db:"code"`. A single string` primary key is supported.

Examples

See examples/ for working samples:

  • getting-started -- minimal CRUD
  • sqlite-todo -- SQLite dialect, tag indexes, cursor pagination
  • model-features -- soft delete, versioning, audit, and JSON/array columns
  • value-objects -- single-column (Email) and multi-column (Money) value objects
  • enums -- string and int enums with generated DB constraints
  • relationships -- associations and includes
  • bulk-ops -- batch operations
  • api -- dynamic query composition from HTTP parameters (IQueryable-style conditional Where chaining)
  • multi-model -- domain events, transaction hooks
  • outbox -- transactional outbox: events persisted atomically with data, plus the lease-based relay
  • inbox -- duplicate delivery suppressed: Claim in the handler's transaction, Fail, and Purge
  • event-sourcing -- event store, optimistic concurrency, a projection with checkpoints, and a replay
  • feature-slices -- one module for each slice: per-slice migrations, ApplyMigrationsFS, and db.Modules.<Slice>
  • observability -- structured query logging, tracing spans, and dev-mode diagnostics
  • uuid-keys -- application-assigned UUIDv7 primary keys
  • composite-keys -- a multi-column, application-assigned primary key
  • internals -- what codegen produces, hand-written, to see the machinery

Primary keys: integer auto-increment by default; use drel.Model[uuid.UUID] for app-assigned UUIDv7 (stamped at Add()), drel.Model[string] for a named single-column key (for example db:"code"), or a comparable struct for a composite key (application-assigned, one column per exported field).

Dialects

  • Postgres — direct pgx, auto-detected from postgres:// DSNs.
  • SQLite — pure-Go modernc.org/sqlite, auto-detected from file:, sqlite://, :memory:, or *.db DSNs.
  • LibSQL/Turso — libsql:///https:///wss:// DSNs, no build flags or imports required. Verified end-to-end against a real libSQL server over HTTP. Prefer libsql:///https:// over ws:// for models with time.Time columns.

Limitations

  • Migration renames must be declared, not inferred. Mark a renamed column with db:"new_name,renamed_from=old_name", and a renamed table with renamed_from= on the embedded drel.Model field. Without a marker a rename appears as a drop and an add, which destroys the column's data. drel does not guess renames: a wrong guess is unrecoverable.
  • Index renames are emitted as a drop and a create. This is lossless, so no marker is offered for them.
  • Bulk Set accepts any values — type safety is enforced on column predicates and Find but not on bulk mutation values.
  • True JOIN-based eager loading is intentionally not offered; relationships load via batched split queries (correct for every shape, no cartesian products).
  • A composite-key model cannot be the target of a relationship, and cannot declare has_many, has_one, or many_to_many itself. A foreign key to a multi-column key is not yet emitted, the include loader matches a child foreign key against one parent key value, and a many-to-many pivot needs one column per key field. A composite-key model may declare belongs_to only. Codegen rejects the other kinds at generation time.
  • A composite key must be application-assigned. Auto-increment applies only to a single integer key column.
  • Changing an existing table's primary key is not diffed. Table.PrimaryKey is read only when a new table is created; neither diffTable nor DiffSchemas reads it for an existing table. A composite or renamed key works for a new table. Altering the key of an existing table emits no migration and needs a hand-written one.

License

MIT -- see LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUniqueViolation is returned when an INSERT/UPDATE violates a unique or
	// primary-key constraint.
	ErrUniqueViolation = dberr.ErrUniqueViolation
	// ErrForeignKeyViolation is returned when a foreign-key constraint fails.
	ErrForeignKeyViolation = dberr.ErrForeignKeyViolation
	// ErrNotNullViolation is returned when a NOT NULL column receives NULL.
	ErrNotNullViolation = dberr.ErrNotNullViolation
	// ErrCheckViolation is returned when a CHECK constraint fails.
	ErrCheckViolation = dberr.ErrCheckViolation
	// ErrSerializationFailure is returned for serialization failures / deadlocks
	// under high isolation; the transaction should be retried.
	ErrSerializationFailure = dberr.ErrSerializationFailure
)

Dialect-neutral database error sentinels. Match them with errors.Is on errors returned by SaveChanges, bulk operations, raw Exec/Query, etc. The original driver error (e.g. *pgconn.PgError) is still reachable via errors.As.

if err := uow.SaveChanges(ctx); errors.Is(err, drel.ErrUniqueViolation) {
    return conflict()
}
View Source
var ErrBatchNotExecuted = errors.New("drel: batch result read before Execute ran this query")

ErrBatchNotExecuted is the initial error on every queued BatchResult. It is cleared as Execute runs each item, so reading a result before Execute ran (or for an item that never ran because Execute aborted early) returns this rather than a silent zero value.

View Source
var ErrBatchPartial = errors.New("drel: one or more batched queries failed")

ErrBatchPartial is returned by Execute when the batch completed but at least one queued query failed. Each failing query's own error is available via its BatchResult.Result(); ErrBatchPartial wraps the first failure.

View Source
var ErrBulkDeleteRequiresFilter = fmt.Errorf("drel: BulkDelete requires at least one Where predicate to prevent accidental full-table deletes")

ErrBulkDeleteRequiresFilter is returned when BulkDelete is called without any WHERE predicates or filters.

View Source
var ErrBulkUpdateRequiresFilter = fmt.Errorf("drel: BulkUpdate requires at least one Where predicate to prevent accidental full-table updates")

ErrBulkUpdateRequiresFilter is returned when BulkUpdate is called without any user Where predicate and without AllRows(), to prevent accidental full-table updates. Auto-applied filters (e.g. soft-delete) do not satisfy the guard.

View Source
var ErrConcurrencyConflict = errors.New("drel: concurrency conflict — entity was modified by another transaction")

ErrConcurrencyConflict is returned when an entity was modified by another transaction.

View Source
var ErrCursorColumnNullable = errors.New(
	"drel: cursor pagination over a NULL-valued ordering key requires NullsFirst()/NullsLast() on that column")

ErrCursorColumnNullable is returned when a keyset cursor must page across a NULL-valued ordering key whose NULL placement is not pinned. Pin it with NullsFirst()/NullsLast() on that OrderBy column so paging is deterministic.

View Source
var ErrCursorPaginationNeedsOrderBy = errors.New("drel: cursor pagination requires OrderBy")

ErrCursorPaginationNeedsOrderBy is returned when Page is called without an OrderBy. Keyset pagination requires a deterministic ordering.

View Source
var ErrDuplicateMessage = errors.New("drel: the handler already processed this message")

ErrDuplicateMessage reports that the handler already processed the message. The caller must acknowledge the message to the broker and do no further work.

View Source
var ErrEntityNotTracked = errors.New("drel: entity is not tracked")

ErrEntityNotTracked is returned when an operation requires a tracked entity but none is found.

View Source
var ErrInvalidCursor = errors.New("drel: invalid cursor")

ErrInvalidCursor is returned when a cursor string cannot be decoded.

View Source
var ErrInvalidPageSize = errors.New("drel: page size (Take/Limit) must be > 0")

ErrInvalidPageSize is returned when a pagination page size (Take/Limit) is <= 0.

View Source
var ErrNestedTxOptions = errors.New("drel: transaction options are not allowed on a nested WithTx call")

ErrNestedTxOptions reports that a nested WithTx call carried transaction options. Isolation and read-only belong to the outermost call, because a savepoint cannot change them.

View Source
var ErrNotFound = errors.New("drel: not found")

ErrNotFound is returned when a query expects a result but finds none.

View Source
var ErrOutboxAnonymousEvent = errors.New("drel: outbox event has no qualified type name (nil or anonymous type)")

ErrOutboxAnonymousEvent is returned by the default outbox mapping path when an event value has no resolvable type name (nil, or an anonymous type). Such events would otherwise be written with an empty type, which a relay cannot route. Provide a WithOutboxMapper to map anonymous events explicitly.

View Source
var ErrPaginationNeedsLimit = errors.New("drel: pagination requires Take/Limit to set the page size")

ErrPaginationNeedsLimit is returned when a pagination method is called without Take/Limit.

View Source
var ErrRawArgMismatch = errors.New("drel: raw predicate placeholder/argument count mismatch")

ErrRawArgMismatch is returned by RawErr when the number of ? placeholders in the raw SQL does not match the number of supplied arguments.

View Source
var ErrUnknownProjectionColumn = errors.New("drel: projected column has no matching DTO field (db tag)")

ErrUnknownProjectionColumn is returned by Select/GroupBy when a projected SQL column has no DTO field whose `db` tag matches it. Binding such a column would either error confusingly or, when types align, silently corrupt data, so drel fails loudly instead. Match it with errors.Is.

View Source
var SoftDeleteFilter = NamedFilter{
	Name: "soft_delete",
	Clause: ast.WhereClause{
		Comparison: &ast.ComparisonNode{Column: "deleted_at", Op: ast.OpIsNull},
	},
}

Functions

func ActorFromContext

func ActorFromContext(ctx context.Context) string

ActorFromContext extracts the actor identifier from the context, returning an empty string if none is set.

func Aggregate added in v0.3.0

func Aggregate[R any, T any](ctx context.Context, q projectable[T], agg AggExpr) (R, error)

Aggregate executes a single aggregate function and returns the scalar result. Accepts an engine-level *QueryBuilder[T] or a transaction-bound *TxQueryBuilder[T].

func ContextWithTx added in v0.6.0

func ContextWithTx(ctx context.Context, tx *Tx) context.Context

ContextWithTx returns a copy of ctx that carries tx. WithTx calls it for you. Call it directly when you open the transaction yourself, for example in a test harness or in middleware that owns the transaction. Code below then finds the transaction with FromContext or MustFromContext, and a nested WithTx call joins it through a savepoint.

func EventTypeName added in v0.7.0

func EventTypeName(v any) (string, error)

EventTypeName returns the package-qualified Go type name of an event value (PkgPath + "." + Name), unwrapping a pointer. The outbox and the event store name an event the same way, so one event carries one name everywhere.

It returns ErrOutboxAnonymousEvent for a nil value or an anonymous type, which have no name a consumer can route on.

func GroupBy added in v0.3.0

func GroupBy[R any, T any](ctx context.Context, q projectable[T], groups []GroupSpec, aggs []AliasedAgg, opts ...GroupByOpt) ([]*R, error)

GroupBy executes a GROUP BY query with aggregates, returning results as []*R. Accepts an engine-level *QueryBuilder[T] or a transaction-bound *TxQueryBuilder[T].

func InboxSchema added in v0.6.0

func InboxSchema(table, dialect string) string

InboxSchema returns the CREATE TABLE DDL for an inbox table for the given dialect ("postgres" or "sqlite").

The primary key is the pair of message ID and handler name. A row with processed_at set is a committed success, and it blocks a new claim. A row with processed_at null only counts a failed attempt, and it does not block.

func JSONEqual added in v0.5.0

func JSONEqual[T any](a, b T) bool

JSONEqual reports whether a and b marshal to identical JSON. It is used by generated diff code for JSON struct/non-comparable columns that have no cheap slices.Equal/maps.Equal form. On a marshal error it conservatively reports false so the change is recorded (a write is safer than silently skipping it).

func NormalizeIntKey added in v0.5.0

func NormalizeIntKey(v any) any

NormalizeIntKey collapses int64/int32 to int so pivot keys match an int PK.

func NormalizeUUIDKey added in v0.5.0

func NormalizeUUIDKey(v any) any

NormalizeUUIDKey converts a UUID primary-key value scanned as a raw driver type into a uuid.UUID so it compares equal to PKValue. It accepts uuid.UUID, [16]byte, []byte (16 bytes), and the canonical string form.

func OutboxSchema added in v0.3.0

func OutboxSchema(table, dialect string) string

OutboxSchema returns CREATE TABLE (and a partial index) DDL for an outbox table for the given dialect ("postgres" or "sqlite"). The payload column is TEXT for portability (JSON is stored as a string); processed_at is left for a relay to stamp.

The lease columns (claimed_by, claimed_until, attempts, last_error, dead_at) let more than one Relay replica poll one table without publishing a message two times. partition_key orders the publish inside one aggregate.

A partial index on open rows is emitted so the relay claim

SELECT id FROM <table> WHERE processed_at IS NULL AND dead_at IS NULL ORDER BY id;

uses an index seek rather than a sequential scan that grows with total outbox size (processed rows are typically retained for audit).

func RawQuery added in v0.3.0

func RawQuery[T any](ctx context.Context, e *Engine, sql string, args ...any) ([]*T, error)

RawQuery executes arbitrary SQL and scans results into []*T. Uses $1, $2 placeholder format — rewritten to ? for SQLite automatically.

IMPORTANT: results are bound to T's fields in struct-declaration order (the `db`-tagged fields, top to bottom), NOT by matching result-set column names. You MUST write the SELECT column list in the same order as T declares its tagged fields, or values land in the wrong fields. Unlike Select/GroupBy, which bind by name, RawQuery cannot inspect the result-set column names today.

func RawQueryRow added in v0.3.0

func RawQueryRow[T any](ctx context.Context, e *Engine, sql string, args ...any) (*T, error)

RawQueryRow executes arbitrary SQL expecting exactly one row, scanned into *T. Uses $1, $2 placeholder format — rewritten to ? for SQLite automatically.

IMPORTANT: like RawQuery, the single row is bound to T's fields in struct-declaration order, NOT by result-set column name. Order the SELECT columns to match T's tagged-field order.

func Select added in v0.3.0

func Select[R any, T any](ctx context.Context, q projectable[T], cols ...ColumnRef) ([]*R, error)

Select executes a projection query, returning only specified columns into DTO type R. It accepts either an engine-level *QueryBuilder[T] or a transaction-bound *TxQueryBuilder[T]; in the latter case the projection runs inside the transaction.

func SetKeyGenerator added in v0.4.0

func SetKeyGenerator[T any](meta ModelMeta[T], gen func() any)

SetKeyGenerator overrides the key generator for a model at runtime. Setting a generator implies KeyAppAssigned. Pass nil to require app-supplied keys.

func SetKeyStrategy added in v0.4.0

func SetKeyStrategy[T any](meta ModelMeta[T], s KeyStrategy)

SetKeyStrategy overrides the key strategy for a model at runtime. It must be called before the model's repositories are used (typically at startup).

func UUIDv7Key added in v0.4.0

func UUIDv7Key() any

UUIDv7Key returns a fresh time-ordered UUIDv7 as an any, suitable as a ModelMeta.GenerateKey value. It is the default generator for uuid.UUID keys.

func WithActor

func WithActor(ctx context.Context, actor string) context.Context

WithActor returns a new context carrying the given actor identifier.

Types

type AfterCommitHook

type AfterCommitHook func(ctx context.Context, events []any)

type AggExpr added in v0.3.0

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

AggExpr represents an aggregate function call.

func Avg added in v0.3.0

func Avg(col ColumnRef) AggExpr

Avg creates an AVG aggregate expression.

func Count added in v0.5.0

func Count() AggExpr

Count is an alias for CountStar reading naturally as Count().

func CountCol added in v0.3.0

func CountCol(col ColumnRef) AggExpr

CountCol creates a COUNT aggregate expression for a specific column.

func CountDistinct added in v0.5.0

func CountDistinct(col ColumnRef) AggExpr

CountDistinct creates a COUNT(DISTINCT col) aggregate expression that counts the number of distinct non-NULL values in col.

func CountStar added in v0.5.0

func CountStar() AggExpr

CountStar creates a COUNT(*) aggregate expression that counts all rows in the group (or the whole result set). Unlike CountCol it counts NULL rows too.

func Max added in v0.3.0

func Max(col ColumnRef) AggExpr

Max creates a MAX aggregate expression.

func Min added in v0.3.0

func Min(col ColumnRef) AggExpr

Min creates a MIN aggregate expression.

func Sum added in v0.3.0

func Sum(col ColumnRef) AggExpr

Sum creates a SUM aggregate expression. Over an empty set it returns 0 via COALESCE so it scans cleanly into a non-nullable numeric result type.

type AliasedAgg added in v0.3.0

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

AliasedAgg pairs an aggregate with a result alias.

func As added in v0.3.0

func As(alias string, agg AggExpr) AliasedAgg

As pairs an aggregate expression with a column alias for the result.

type Audit

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

func (*Audit) AuditPtrs

func (a *Audit) AuditPtrs() (*string, *string)

func (Audit) CreatedBy

func (a Audit) CreatedBy() string

func (Audit) UpdatedBy

func (a Audit) UpdatedBy() string

type Batch added in v0.3.0

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

Batch groups multiple read queries so they execute in a single network round-trip when the driver supports pipelining (pgx), falling back to sequential execution otherwise. Queue queries with BatchAll/BatchFirst/ BatchCount, call Execute, then read each typed result via Result.

batch := db.NewBatch()
users := drel.BatchAll(batch, db.Users.Where(Users.Active.IsTrue()))
count := drel.BatchCount(batch, db.Users)
if err := batch.Execute(ctx); err != nil { ... }
us, _ := users.Result()
n, _ := count.Result()

A batch created with Engine.NewBatch pipelines on pgx (and runs sequentially on SQLite/libSQL). A batch created with Tx.NewBatch always runs sequentially on the transaction connection so it observes the transaction's uncommitted writes; a UnitOfWork batches the same way via the Tx it opens during a flush. Batch results are read-only and are NOT snapshotted into a change tracker: to mutate-and-save loaded entities, load them via Find/All/Include instead.

func (*Batch) Execute added in v0.3.0

func (b *Batch) Execute(ctx context.Context) error

Execute runs all queued queries. On a pipelining driver they are sent in one round-trip and their results read in order; otherwise they run sequentially. Execute drains every queued query even if an earlier one fails: each query's own error (and value) is stored on its BatchResult, so Result() faithfully reports per-query success or failure. If any query failed, Execute returns ErrBatchPartial wrapping the first failure; otherwise it returns nil.

type BatchResult added in v0.3.0

type BatchResult[T any] struct {
	// contains filtered or unexported fields
}

BatchResult holds the typed outcome of a batched query, available after the batch has been executed.

func BatchAll added in v0.3.0

func BatchAll[T any](b *Batch, q *QueryBuilder[T]) *BatchResult[[]*T]

BatchAll queues a query returning all matching rows.

func BatchCount added in v0.3.0

func BatchCount[T any](b *Batch, q *QueryBuilder[T]) *BatchResult[int]

BatchCount queues a COUNT query.

func BatchFirst added in v0.3.0

func BatchFirst[T any](b *Batch, q *QueryBuilder[T]) *BatchResult[*T]

BatchFirst queues a query returning the first matching row; Result returns ErrNotFound if there is none.

func (*BatchResult[T]) Result added in v0.3.0

func (r *BatchResult[T]) Result() (T, error)

Result returns the query's result and any error encountered while scanning it.

type BeforeCommitHook

type BeforeCommitHook func(ctx context.Context, tx *Tx, events []any) error

type BoolColumn

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

func NewBoolCol

func NewBoolCol(name string) BoolColumn

func (BoolColumn) Asc

func (c BoolColumn) Asc() OrderExpr

func (BoolColumn) ColRef added in v0.3.0

func (c BoolColumn) ColRef() ColumnRef

ColRef returns a ColumnRef for use with Select projections and aggregates.

func (BoolColumn) Desc

func (c BoolColumn) Desc() OrderExpr

func (BoolColumn) Eq

func (c BoolColumn) Eq(v bool) Predicate

func (BoolColumn) IsFalse

func (c BoolColumn) IsFalse() Predicate

func (BoolColumn) IsNotNull

func (c BoolColumn) IsNotNull() Predicate

func (BoolColumn) IsNull

func (c BoolColumn) IsNull() Predicate

func (BoolColumn) IsTrue

func (c BoolColumn) IsTrue() Predicate

func (BoolColumn) NEQ

func (c BoolColumn) NEQ(v bool) Predicate

func (BoolColumn) Name

func (c BoolColumn) Name() string

type ClaimedMessage added in v0.6.0

type ClaimedMessage struct {
	ID           int64
	Type         string
	Payload      []byte
	PartitionKey string
	// Attempts counts the claims of this message, this one included. The first
	// delivery carries 1.
	Attempts int
}

ClaimedMessage is one outbox row that a Relay holds a lease on.

type Column

type Column[T any] struct {
	// contains filtered or unexported fields
}

func NewCol

func NewCol[T any](name string) Column[T]

func (Column[T]) Asc

func (c Column[T]) Asc() OrderExpr

func (Column[T]) ColRef added in v0.3.0

func (c Column[T]) ColRef() ColumnRef

ColRef returns a ColumnRef for use with Select projections and aggregates.

func (Column[T]) Desc

func (c Column[T]) Desc() OrderExpr

func (Column[T]) Eq

func (c Column[T]) Eq(v T) Predicate

func (Column[T]) In

func (c Column[T]) In(vs ...T) Predicate

func (Column[T]) IsNotNull

func (c Column[T]) IsNotNull() Predicate

func (Column[T]) IsNull

func (c Column[T]) IsNull() Predicate

func (Column[T]) NEQ

func (c Column[T]) NEQ(v T) Predicate

func (Column[T]) Name

func (c Column[T]) Name() string

func (Column[T]) NotIn

func (c Column[T]) NotIn(vs ...T) Predicate

type ColumnRef added in v0.3.0

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

ColumnRef identifies a database column for use in Select projections.

func ColRef added in v0.3.0

func ColRef(name string) ColumnRef

ColRef creates a ColumnRef from a column name string.

func (ColumnRef) Name added in v0.3.0

func (c ColumnRef) Name() string

Name returns the column name.

type ComparableColumn added in v0.5.0

type ComparableColumn[T any] struct {
	// contains filtered or unexported fields
}

ComparableColumn is a column type for values that support ordering but are not covered by the built-in numeric/string/time-specific column types — for example uuid.UUID, *time.Time, or custom value objects that implement a total order. It provides the full range-operator set (GT/GTE/LT/LTE/Between).

func NewComparableCol added in v0.5.0

func NewComparableCol[T any](name string) ComparableColumn[T]

func (ComparableColumn[T]) Asc added in v0.5.0

func (c ComparableColumn[T]) Asc() OrderExpr

func (ComparableColumn[T]) Between added in v0.5.0

func (c ComparableColumn[T]) Between(low, high T) Predicate

Between returns a predicate matching rows where the column value is between low and high inclusive (SQL BETWEEN low AND high).

func (ComparableColumn[T]) ColRef added in v0.5.0

func (c ComparableColumn[T]) ColRef() ColumnRef

func (ComparableColumn[T]) Desc added in v0.5.0

func (c ComparableColumn[T]) Desc() OrderExpr

func (ComparableColumn[T]) Eq added in v0.5.0

func (c ComparableColumn[T]) Eq(v T) Predicate

func (ComparableColumn[T]) GT added in v0.5.0

func (c ComparableColumn[T]) GT(v T) Predicate

func (ComparableColumn[T]) GTE added in v0.5.0

func (c ComparableColumn[T]) GTE(v T) Predicate

func (ComparableColumn[T]) In added in v0.5.0

func (c ComparableColumn[T]) In(vs ...T) Predicate

func (ComparableColumn[T]) IsNotNull added in v0.5.0

func (c ComparableColumn[T]) IsNotNull() Predicate

func (ComparableColumn[T]) IsNull added in v0.5.0

func (c ComparableColumn[T]) IsNull() Predicate

func (ComparableColumn[T]) LT added in v0.5.0

func (c ComparableColumn[T]) LT(v T) Predicate

func (ComparableColumn[T]) LTE added in v0.5.0

func (c ComparableColumn[T]) LTE(v T) Predicate

func (ComparableColumn[T]) NEQ added in v0.5.0

func (c ComparableColumn[T]) NEQ(v T) Predicate

func (ComparableColumn[T]) Name added in v0.5.0

func (c ComparableColumn[T]) Name() string

func (ComparableColumn[T]) NotIn added in v0.5.0

func (c ComparableColumn[T]) NotIn(vs ...T) Predicate

type CursorPage added in v0.3.0

type CursorPage[T any] struct {
	Items          []*T
	NextCursor     string // empty when there are no more results
	PreviousCursor string // empty when there is no previous page
	HasMore        bool
	HasPrev        bool
}

CursorPage holds a page of results from keyset (cursor) pagination.

type Engine

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

Engine holds a database driver and dialect for executing queries.

func NewEngine

func NewEngine(dsn string, opts ...Option) (*Engine, error)

NewEngine creates a new Engine connected to the given DSN. The dialect and driver are auto-detected from the DSN unless overridden with WithDriver or WithDialect.

libSQL/Turso caveat: the websocket transport (ws:// or wss://) returns DATETIME columns as strings and corrupts time.Time scans (including created_at/updated_at). Prefer a libsql:// or https:// DSN for models with time.Time fields; NewEngine logs a warning when a ws://-scheme DSN is opened.

func (*Engine) ApplyMigrations added in v0.5.0

func (e *Engine) ApplyMigrations(ctx context.Context, dir string) (int, error)

ApplyMigrations runs all pending up-migrations from dir against the engine's own connection pool, returning the number applied. It reuses the standard migration runner (tracking table + checksums), so it is safe to call against an in-memory SQLite engine — the migrations land in the same database the engine queries. The migration files must be written for the engine's dialect (see Engine.DialectName); this is the caller's responsibility.

func (*Engine) ApplyMigrationsFS added in v0.7.0

func (e *Engine) ApplyMigrationsFS(ctx context.Context, fsys ...fs.FS) (int, error)

ApplyMigrationsFS applies the pending migrations of one or more embedded migration sets and returns how many it applied. Each feature slice embeds its own migrations with //go:embed, and drel merges the sets in version order, so the migrations run in the order they were written and not in the order the arguments appear.

//go:embed *.sql
var FS embed.FS            // generated in each module

n, err := db.ApplyMigrationsFS(ctx, posts.FS, users.FS, billing.FS)

A version that appears in two modules is an error that names both files. Two slices generated in the same second, or on two branches, produce the same version, and there is no correct order between them.

The migration files must be written for the engine's dialect.

func (*Engine) Close

func (e *Engine) Close()

Close shuts down the underlying database connection and any read replicas.

func (*Engine) DialectName added in v0.5.0

func (e *Engine) DialectName() string

DialectName reports the engine's dialect: "postgres" or "sqlite". libSQL/Turso reuse the SQLite dialect, so they also report "sqlite".

func (*Engine) Exec

func (e *Engine) Exec(ctx context.Context, sql string, args ...any) (int64, error)

Exec executes a raw SQL statement and returns the number of rows affected. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL).

func (*Engine) HealthCheck added in v0.5.0

func (e *Engine) HealthCheck(ctx context.Context) error

HealthCheck pings the primary and every registered read replica, returning a joined error naming any unreachable endpoint. Wire it into a readiness probe (e.g. /readyz). A replica failure does not mask a primary failure: both are reported.

func (*Engine) NewBatch added in v0.3.0

func (e *Engine) NewBatch() *Batch

NewBatch creates an empty query batch bound to this engine.

func (*Engine) OnAfterCommit

func (e *Engine) OnAfterCommit(hook AfterCommitHook)

func (*Engine) OnBeforeCommit

func (e *Engine) OnBeforeCommit(hook BeforeCommitHook)

func (*Engine) OnQuery

func (e *Engine) OnQuery(hook QueryHook)

OnQuery registers a hook that is called after every query execution.

func (*Engine) Ping added in v0.5.0

func (e *Engine) Ping(ctx context.Context) error

Ping verifies a working connection to the primary database. Wire it into a liveness probe (e.g. /healthz). It respects ctx cancellation/deadline.

func (*Engine) Query

func (e *Engine) Query(ctx context.Context, sql string, args ...any) (Rows, error)

Query executes a raw SQL query and returns the result rows. The caller must close the returned Rows when done. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL).

func (*Engine) QueryRow

func (e *Engine) QueryRow(ctx context.Context, sql string, args ...any) Row

QueryRow executes a raw SQL query that is expected to return at most one row. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL).

func (*Engine) Stats added in v0.5.0

func (e *Engine) Stats() PoolStats

Stats returns a snapshot of the primary connection pool's utilisation.

func (*Engine) Transaction

func (e *Engine) Transaction(ctx context.Context, fn func(tx *Tx) error, opts ...TxOption) (retErr error)

Transaction runs fn inside a database transaction. If fn returns nil the transaction is committed; otherwise it is rolled back. Pending changes in the Tx change tracker are flushed automatically before commit.

func (*Engine) TransactionWithRetry added in v0.5.0

func (e *Engine) TransactionWithRetry(ctx context.Context, fn func(tx *Tx) error, opts ...TxOption) error

TransactionWithRetry runs fn in a transaction, re-running it on ErrSerializationFailure (deadlock / SQLSTATE 40001 / SQLITE_BUSY) up to MaxAttempts, with exponential backoff + jitter, honouring ctx cancellation between attempts. Configure the bounds with WithRetry; without it, defaults apply (3 attempts). Each attempt gets a fresh Tx and change tracker, so fn MUST rebuild any entities it adds/edits on every call — do not reuse entity pointers across attempts (see the savepoint in-memory caveat on Tx.Savepoint).

Any non-serialization error (or success) returns immediately.

func (*Engine) UseOutbox added in v0.3.0

func (e *Engine) UseOutbox(table string, opts ...OutboxOption)

UseOutbox writes domain events to a transactional outbox table on every SaveChanges, within the same transaction as the changes. The table must exist (see OutboxSchema) with at least (type, payload) columns; payload is stored as JSON text. Events are dispatched to after-commit handlers as usual in addition to being persisted.

func (*Engine) WithTx added in v0.6.0

func (e *Engine) WithTx(ctx context.Context, fn func(ctx context.Context) error, opts ...TxOption) error

WithTx runs fn inside a database transaction and puts that transaction in the context it passes to fn. Retrieve it with FromContext or MustFromContext.

The transaction commits when fn returns nil. It rolls back when fn returns an error or panics.

A nested WithTx call on the same engine reuses the transaction from the context and opens a savepoint. It does not begin a second transaction, and it does not commit. An error from the nested call rolls back to the savepoint only, and the outer call still decides the commit. A nested call returns ErrNestedTxOptions if it carries transaction options.

The behaviour is the same for Postgres, SQLite and LibSQL.

type EntityState added in v0.3.0

type EntityState int

EntityState describes a tracked entity's lifecycle state within a Tx.

const (
	// StateUnchanged marks an entity with no pending changes.
	StateUnchanged EntityState = iota
	// StateAdded marks an entity to be INSERTed on the next flush.
	StateAdded
	// StateModified marks an entity to be UPDATEd on the next flush.
	StateModified
	// StateDeleted marks an entity to be deleted on the next flush.
	StateDeleted
)

type EventRecorder

type EventRecorder interface {
	PendingEvents() []any
	ClearEvents()
}

type FieldChange

type FieldChange struct {
	Column string
	Value  any
}

FieldChange represents a single column value that has changed on an entity.

type GroupByOpt added in v0.3.0

type GroupByOpt func(*groupByConfig)

GroupByOpt is an option for GroupBy queries.

func Having added in v0.3.0

func Having(pred Predicate) GroupByOpt

Having adds a HAVING clause to a GroupBy query.

type GroupSpec added in v0.3.0

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

GroupSpec specifies a GROUP BY column.

func Group added in v0.3.0

func Group(col ColumnRef) GroupSpec

Group creates a GroupSpec from a ColumnRef.

type Inbox added in v0.6.0

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

Inbox suppresses a duplicate delivery. A message broker delivers at least once, so one message can reach a handler more than one time. Claim writes a dedupe row inside the caller's transaction, next to the application write. The two therefore commit together, or neither commits.

The key is the pair of message ID and handler name. Two handlers can process one message. One handler cannot process one message two times.

func NewInbox added in v0.6.0

func NewInbox(e *Engine, table string) *Inbox

NewInbox creates an inbox on one table. The table must exist. InboxSchema emits it.

func (*Inbox) Claim added in v0.6.0

func (i *Inbox) Claim(ctx context.Context, messageID, handler string) error

Claim writes the dedupe row for one message and one handler inside the transaction that WithTx put in ctx. Call it first in the handler, before the application write.

It returns ErrDuplicateMessage when the handler already processed the message. Acknowledge the message and stop.

It panics when ctx carries no transaction. A dedupe row outside the application transaction gives no protection at all, so this is a wiring fault and it must fail at once.

A handler that fails must roll the transaction back. The dedupe row then disappears with it, and the broker can deliver the message again. Record the failure with Fail after the rollback.

func (*Inbox) Fail added in v0.6.0

func (i *Inbox) Fail(ctx context.Context, messageID, handler string, cause error) error

Fail records a failed attempt. Call it after the handler's transaction rolled back, so it runs on the engine and not on that transaction. The row it writes counts the attempt and holds the error. It does not block a later claim.

A message that the handler already processed is left alone, so a late error cannot reopen it.

func (*Inbox) Purge added in v0.6.0

func (i *Inbox) Purge(ctx context.Context, before time.Time) (int64, error)

Purge deletes the rows received before a time and returns the count. Run it on a schedule to hold the table small.

Purge only beyond the retention of the message broker. A purged row no longer suppresses its message, so an older delivery would run a second time.

type IncludableQuery

type IncludableQuery[T any] struct {
	// contains filtered or unexported fields
}

IncludableQuery constructs queries that eagerly load related entities. The root query may be refined with Where/OrderBy/Limit/Skip/Take exactly like a plain QueryBuilder; the configured relationships are loaded via split queries after the root result set is materialized.

func (*IncludableQuery[T]) All

func (q *IncludableQuery[T]) All(ctx context.Context) ([]*T, error)

All returns all matching records (honoring Where/OrderBy/Limit) and loads included relationships.

func (*IncludableQuery[T]) Find

func (q *IncludableQuery[T]) Find(ctx context.Context, id any) (*T, error)

Find looks up a single record by primary key and loads included relationships.

func (*IncludableQuery[T]) First added in v0.3.0

func (q *IncludableQuery[T]) First(ctx context.Context) (*T, error)

First returns the first matching record (honoring Where/OrderBy) and loads included relationships, or ErrNotFound if none match.

func (*IncludableQuery[T]) Include added in v0.3.0

func (q *IncludableQuery[T]) Include(rels ...IncludeSpec) *IncludableQuery[T]

Include adds more relationships to eagerly load.

func (*IncludableQuery[T]) Limit added in v0.3.0

func (q *IncludableQuery[T]) Limit(n int) *IncludableQuery[T]

Limit restricts the number of root records returned.

func (*IncludableQuery[T]) OrderBy added in v0.3.0

func (q *IncludableQuery[T]) OrderBy(exprs ...OrderExpr) *IncludableQuery[T]

OrderBy sets the ordering for the root query.

func (*IncludableQuery[T]) Primary added in v0.5.0

func (q *IncludableQuery[T]) Primary() *IncludableQuery[T]

Primary forces both the root query and all included sub-queries to read from the primary connection instead of a read replica. Use it for read-your-writes consistency immediately after a write. No effect when no replicas configured.

func (*IncludableQuery[T]) Skip added in v0.3.0

func (q *IncludableQuery[T]) Skip(n int) *IncludableQuery[T]

Skip sets the offset for the root query.

func (*IncludableQuery[T]) Take added in v0.3.0

func (q *IncludableQuery[T]) Take(n int) *IncludableQuery[T]

Take restricts the number of root records returned (alias for Limit).

func (*IncludableQuery[T]) Unscoped added in v0.3.0

func (q *IncludableQuery[T]) Unscoped() *IncludableQuery[T]

Unscoped removes all global filters from the root query.

func (*IncludableQuery[T]) Where added in v0.3.0

func (q *IncludableQuery[T]) Where(pred Predicate) *IncludableQuery[T]

Where adds a filter predicate to the root query.

func (*IncludableQuery[T]) WithoutFilter added in v0.3.0

func (q *IncludableQuery[T]) WithoutFilter(name string) *IncludableQuery[T]

WithoutFilter removes the named global filter from the root query.

type IncludeSpec

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

IncludeSpec wraps a RelationInfo for use with Include queries.

func NewIncludeSpec

func NewIncludeSpec(rel *RelationInfo) IncludeSpec

NewIncludeSpec creates an IncludeSpec from a RelationInfo.

func (IncludeSpec) Limit added in v0.3.0

func (s IncludeSpec) Limit(n int) IncludeSpec

Limit returns a copy of the IncludeSpec that limits the number of related entities loaded per parent. For multi-parent loads this is enforced with a window function (ROW_NUMBER() partitioned by the foreign key); for a single parent it is a plain LIMIT.

func (IncludeSpec) OrderBy added in v0.3.0

func (s IncludeSpec) OrderBy(exprs ...OrderExpr) IncludeSpec

OrderBy returns a copy of the IncludeSpec with the given ordering applied to the related entities query.

func (IncludeSpec) Then added in v0.3.0

func (s IncludeSpec) Then(children ...IncludeSpec) IncludeSpec

Then returns a copy of the IncludeSpec that also eagerly loads the given relationships on the related entities, enabling nested includes such as Users.Posts.Then(Posts.Tags). The child specs describe relationships on the related model (here Post), and may themselves be nested with Then. Nested loading uses split queries on each level, so it never produces a cartesian product regardless of depth.

func (IncludeSpec) Unscoped

func (s IncludeSpec) Unscoped() IncludeSpec

Unscoped returns a copy of the IncludeSpec that bypasses global filters (e.g. soft delete) when loading the related entities.

func (IncludeSpec) Where added in v0.3.0

func (s IncludeSpec) Where(pred Predicate) IncludeSpec

Where returns a copy of the IncludeSpec with an additional filter predicate applied to the related entities query.

func (IncludeSpec) WithoutFilter added in v0.3.0

func (s IncludeSpec) WithoutFilter(name string) IncludeSpec

WithoutFilter returns a copy of the IncludeSpec that drops the named global filter when loading the related entities (the others still apply).

type IsolationLevel

type IsolationLevel int

IsolationLevel represents the transaction isolation level.

const (
	// ReadCommitted is the default isolation level.
	ReadCommitted IsolationLevel = iota
	// RepeatableRead prevents non-repeatable reads.
	RepeatableRead
	// Serializable provides full serializability.
	Serializable
)

type JSON added in v0.5.0

type JSON[T any] struct {
	V *T
}

JSON is a generic adapter that maps a Go value of type T to a JSON-encoded database column (Postgres jsonb / SQLite TEXT). It implements driver.Valuer (marshals *V on write) and sql.Scanner (unmarshals into *V on read), so it round-trips on both pgx and database/sql without any reflection in the ORM.

Generated code wraps slice/map/struct columns as JSON{V: &p.Field}. V is a pointer so the value-receiver Scan can populate the underlying field.

func (JSON[T]) Scan added in v0.5.0

func (j JSON[T]) Scan(src any) error

Scan unmarshals a []byte or string source into *V. A nil source resets *V to its zero value.

func (JSON[T]) Value added in v0.5.0

func (j JSON[T]) Value() (driver.Value, error)

Value marshals *V to JSON bytes. A nil pointer encodes as SQL NULL.

type JoinOn added in v0.5.0

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

JoinOn is a fully-qualified ON expression for a JOIN clause. It is produced by QualifiedCol.EqCol and consumed by QueryBuilder.LeftJoin/InnerJoin.

type KeyStrategy added in v0.4.0

type KeyStrategy uint8

KeyStrategy selects how a model's primary key is produced.

const (
	// KeyAutoIncrement uses a database-generated key (SERIAL / AUTOINCREMENT)
	// read back after INSERT. This is the default for integer primary keys and
	// is the historical behavior.
	KeyAutoIncrement KeyStrategy = iota
	// KeyAppAssigned uses a key known before INSERT — generated by a registered
	// generator (e.g. UUIDv7) or set by application code. The key is written as
	// part of the INSERT and is never read back.
	KeyAppAssigned
)

type Model

type Model[K comparable] struct {
	// contains filtered or unexported fields
}

func (*Model[K]) ClearEvents

func (m *Model[K]) ClearEvents()

func (Model[K]) CreatedAt

func (m Model[K]) CreatedAt() time.Time

func (Model[K]) ID

func (m Model[K]) ID() K

func (*Model[K]) PendingEvents

func (m *Model[K]) PendingEvents() []any

func (*Model[K]) RecordEvent

func (m *Model[K]) RecordEvent(event any)

RecordEvent stages a domain event on the entity. Recorded events are dispatched (to after-commit handlers and any outbox) when the entity is persisted by SaveChanges — i.e. when it is inserted, updated, or deleted in that unit of work. Recording an event on an entity that is otherwise unchanged (no field mutation, not added or removed) does not, on its own, cause a flush, so its events are not dispatched until the entity is also mutated. Record events alongside the mutation that produced them.

func (*Model[K]) ScanPtrs

func (m *Model[K]) ScanPtrs() (*K, *time.Time, *time.Time)

ScanPtrs returns pointers to the base model fields for use in row scanning. Generated code calls this to obtain scan destinations for id, createdAt, and updatedAt without directly accessing unexported fields.

func (*Model[K]) SetID added in v0.4.0

func (m *Model[K]) SetID(id K)

SetID sets the primary key. Used by generated key strategies (app-assigned keys) and by application code that supplies its own keys.

func (Model[K]) UpdatedAt

func (m Model[K]) UpdatedAt() time.Time

type ModelMeta

type ModelMeta[T any] struct {
	Table   string
	Columns []string
	// PKColumns lists every primary key column, in key order. Generated code
	// always sets it.
	PKColumns []string
	// KeyValues splits a primary key value into one value per PKColumns entry,
	// in the same order. When nil the key is a single value.
	KeyValues     func(key any) []any
	Scan          func(Row) (*T, error)
	Snapshot      func(*T) any
	Diff          func(*T, any) []FieldChange
	PKValue       func(*T) any
	InsertColumns func(*T) ([]string, []any)
	ScanReturning func(*T, Row) error
	ScanGenerated func(*T, Row) error // scans created_at, updated_at only (app-assigned)
	KeyStrategy   KeyStrategy
	GenerateKey   func() any
	SetKey        func(*T, any)
	KeyIsZero     func(*T) bool
	ColumnValue   func(*T, int) any
	// NormalizeKey converts a primary-key value scanned as a raw driver type
	// (e.g. [16]byte/string for a UUID, int64 for an integer) into the canonical
	// Go key type returned by PKValue, so pivot-table keys compare equal to it.
	// Optional; when nil the loader falls back to int normalization.
	NormalizeKey   func(any) any
	Filters        []NamedFilter
	HasSoftDelete  bool
	HasVersioned   bool
	HasAudit       bool
	VersionValue   func(*T) int
	SetVersion     func(*T, int)
	AuditSetCreate func(*T, string)
	AuditSetUpdate func(*T, string)
}

ModelMeta describes the database mapping for a model type T.

type ModelMetaBase

type ModelMetaBase struct {
	Table          string
	Columns        []string
	PKColumns      []string
	KeyValues      func(key any) []any
	Snapshot       func(entity any) any
	Diff           func(entity any, snapshot any) []FieldChange
	PKValue        func(entity any) any
	InsertColumns  func(entity any) ([]string, []any)
	ScanRow        func(Row) (any, error)
	ScanReturning  func(entity any, row Row) error
	ScanGenerated  func(entity any, row Row) error
	KeyStrategy    KeyStrategy
	GenerateKey    func() any
	SetKey         func(entity any, key any)
	KeyIsZero      func(entity any) bool
	ColumnValue    func(entity any, colIdx int) any
	HasSoftDelete  bool
	HasVersioned   bool
	HasAudit       bool
	VersionValue   func(entity any) int
	SetVersion     func(entity any, v int)
	AuditSetCreate func(entity any, actor string)
	AuditSetUpdate func(entity any, actor string)
	Filters        []NamedFilter
	NormalizeKey   func(any) any
}

ModelMetaBase is the type-erased version of ModelMeta[T], used internally by the change tracker and relationship loading infrastructure.

func ToMetaBase

func ToMetaBase[T any](meta *ModelMeta[T]) *ModelMetaBase

ToMetaBase converts a typed ModelMeta[T] to a type-erased ModelMetaBase.

type MultiColumnMapper

type MultiColumnMapper interface {
	DrelColumns() []string
	DrelValues() ([]any, error)
	DrelScanMulti(vals []any) error
}

MultiColumnMapper is implemented by value objects that map to multiple database columns (e.g., Money → amount + currency).

type NamedFilter

type NamedFilter struct {
	Name   string
	Clause ast.WhereClause
}

type OffsetPage added in v0.3.0

type OffsetPage[T any] struct {
	Items      []*T
	Total      int
	Page       int // 1-based page number
	PageSize   int
	TotalPages int
	HasMore    bool
}

OffsetPage holds a page of results from offset-based pagination.

type Option

type Option func(*engineConfig)

Option configures Engine creation.

func WithAfterCommitErrorSink added in v0.5.0

func WithAfterCommitErrorSink(fn func(ctx context.Context, err error)) Option

WithAfterCommitErrorSink registers a callback that receives errors and recovered panics from after-commit hooks. After-commit hooks run after the commit has already succeeded, so their failures cannot roll back the transaction — durable side-effects belong in the outbox. The sink is the only signal for such failures; if unset they are dropped. The sink itself is recovered, so a panicking sink cannot crash the caller.

func WithAuthToken added in v0.3.0

func WithAuthToken(token string) Option

WithAuthToken sets the authentication token for a libSQL/Turso connection. It is appended to the DSN as the authToken query parameter.

Prefer a libsql:// or https:// DSN over ws://wss:// — the websocket transport corrupts time.Time columns (see NewEngine).

func WithConnMaxIdleTime added in v0.3.2

func WithConnMaxIdleTime(d time.Duration) Option

WithConnMaxIdleTime sets how long a connection may sit idle before being closed.

func WithConnMaxLifetime added in v0.3.2

func WithConnMaxLifetime(d time.Duration) Option

WithConnMaxLifetime sets the maximum lifetime of a pooled connection before it is recycled (useful behind connection poolers / for failover).

func WithContext

func WithContext(ctx context.Context) Option

WithContext sets the context used during engine creation.

func WithDevMode added in v0.3.0

func WithDevMode() Option

WithDevMode enables development-time diagnostics: warnings for unbounded queries (SELECT without LIMIT), likely N+1 access patterns (the same query shape executed repeatedly in a short window), slow queries, and tracked entities that were loaded but never modified. Do not enable in production.

func WithDialect added in v0.3.0

func WithDialect(dia dialect.Dialect) Option

WithDialect overrides the SQL dialect used by the engine. When set, auto-detection is skipped for the dialect.

func WithDriver added in v0.3.0

func WithDriver(drv driver.Driver) Option

WithDriver overrides the driver used by the engine. When set, auto-detection is skipped for the driver.

func WithLogger added in v0.3.0

func WithLogger(logger *slog.Logger) Option

WithLogger attaches a structured logger. Combined with WithQueryLog, WithSlowQueryThreshold, or WithDevMode it emits query and diagnostic logs.

func WithMaxConns added in v0.3.2

func WithMaxConns(n int) Option

WithMaxConns sets the maximum number of open database connections in the pool.

func WithQueryLog added in v0.3.0

func WithQueryLog(enabled bool) Option

WithQueryLog logs every executed query (SQL, arg count, duration) at debug level. Requires a logger (WithLogger); if none is set, slog.Default is used.

func WithQueryTimeout added in v0.5.0

func WithQueryTimeout(d time.Duration) Option

WithQueryTimeout sets a default deadline applied to every engine-level query and exec that does not already carry a shorter deadline. Zero (the default) means no default timeout. It composes with caller-supplied deadlines — the shorter one wins. Per-builder .Timeout(d) overrides this default for that query. The default is NOT auto-applied to queries issued inside an explicit Tx, because a timeout firing mid-transaction aborts the whole transaction; use .Timeout(d) explicitly there if you accept that semantics.

func WithReadReplica added in v0.3.0

func WithReadReplica(dsn string) Option

WithReadReplica registers a read replica. Read queries issued through repositories (not within a transaction, and not forced to Primary) are routed round-robin across all registered replicas; writes and transactions always go to the primary.

func WithSimpleProtocol added in v0.5.0

func WithSimpleProtocol() Option

WithSimpleProtocol makes the Postgres driver use pgx's simple query protocol (no server-side prepared statements), required for PgBouncer transaction or statement pooling. It is a no-op for SQLite and libSQL/Turso. The equivalent DSN escape hatch is "?default_query_exec_mode=simple_protocol".

func WithSlowQueryThreshold added in v0.3.0

func WithSlowQueryThreshold(d time.Duration) Option

WithSlowQueryThreshold logs a warning for any query whose execution exceeds d.

func WithTracer added in v0.3.0

func WithTracer(tracer Tracer) Option

WithTracer emits an OpenTelemetry-style span per executed query.

type OrderExpr

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

func (OrderExpr) NullsFirst added in v0.5.0

func (o OrderExpr) NullsFirst() OrderExpr

NullsFirst orders NULL values before non-NULL values. Chain after Asc()/Desc(). Pin this explicitly when paging over a nullable column so NULL rows are not silently dropped from cursor pages.

func (OrderExpr) NullsLast added in v0.5.0

func (o OrderExpr) NullsLast() OrderExpr

NullsLast orders NULL values after non-NULL values. Chain after Asc()/Desc().

func (OrderExpr) ToAST

func (o OrderExpr) ToAST() ast.OrderByExpr

type OrderedColumn

type OrderedColumn[T cmp.Ordered] struct {
	// contains filtered or unexported fields
}

func NewOrderedCol

func NewOrderedCol[T cmp.Ordered](name string) OrderedColumn[T]

func (OrderedColumn[T]) Asc

func (c OrderedColumn[T]) Asc() OrderExpr

func (OrderedColumn[T]) Between

func (c OrderedColumn[T]) Between(low, high T) Predicate

func (OrderedColumn[T]) ColRef added in v0.3.0

func (c OrderedColumn[T]) ColRef() ColumnRef

ColRef returns a ColumnRef for use with Select projections and aggregates.

func (OrderedColumn[T]) Desc

func (c OrderedColumn[T]) Desc() OrderExpr

func (OrderedColumn[T]) Eq

func (c OrderedColumn[T]) Eq(v T) Predicate

func (OrderedColumn[T]) GT

func (c OrderedColumn[T]) GT(v T) Predicate

func (OrderedColumn[T]) GTE

func (c OrderedColumn[T]) GTE(v T) Predicate

func (OrderedColumn[T]) In

func (c OrderedColumn[T]) In(vs ...T) Predicate

func (OrderedColumn[T]) IsNotNull

func (c OrderedColumn[T]) IsNotNull() Predicate

func (OrderedColumn[T]) IsNull

func (c OrderedColumn[T]) IsNull() Predicate

func (OrderedColumn[T]) LT

func (c OrderedColumn[T]) LT(v T) Predicate

func (OrderedColumn[T]) LTE

func (c OrderedColumn[T]) LTE(v T) Predicate

func (OrderedColumn[T]) NEQ

func (c OrderedColumn[T]) NEQ(v T) Predicate

func (OrderedColumn[T]) Name

func (c OrderedColumn[T]) Name() string

func (OrderedColumn[T]) NotIn

func (c OrderedColumn[T]) NotIn(vs ...T) Predicate

type OutboxMessage added in v0.3.0

type OutboxMessage struct {
	Type         string
	Payload      any
	PartitionKey string
}

OutboxMessage is a row written to the transactional outbox. Type identifies the event kind; Payload is JSON-serialized into the outbox table's payload column. Messages are written within the same transaction as the changes that produced the events, giving exactly-once hand-off to an external relay (Debezium CDC, a polling worker, etc.). PartitionKey orders the relay publish. Messages that carry the same key are published in id order. Messages with different keys can go in parallel. Leave it empty when the message needs no order; the column then stays NULL.

type OutboxOption added in v0.3.0

type OutboxOption func(*outboxConfig)

OutboxOption configures UseOutbox.

func WithOutboxMapper added in v0.3.0

func WithOutboxMapper(fn func(event any) (OutboxMessage, bool)) OutboxOption

WithOutboxMapper customizes how domain events are mapped to outbox messages. Return ok=false to skip an event. The default maps every event to a message whose Type is the event's package-qualified Go type name (PkgPath + "." + Name, e.g. "github.com/acme/orders.OrderPlaced") and whose Payload is the event itself. The qualified name avoids cross-package collisions in the feature-slice layout where two packages may each define an "OrderPlaced".

type PoolStats added in v0.5.0

type PoolStats = driver.PoolStat

PoolStats is a dialect-neutral snapshot of connection-pool utilisation, suitable for a metrics endpoint. AcquiredConns is the number of connections currently in use; IdleConns are available; TotalConns is the sum currently held; MaxConns is the configured ceiling (0 means driver default).

type Predicate

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

func And

func And(preds ...Predicate) Predicate

func Not

func Not(pred Predicate) Predicate

func Or

func Or(preds ...Predicate) Predicate

func Raw

func Raw(sql string, args ...any) Predicate

Raw creates a predicate from a raw SQL expression with bound arguments. Use ? as placeholder for each argument; they are rewritten to $N for Postgres. Placeholder counting is quote-aware: a '?' inside a single-quoted string, double-quoted identifier, or dollar-quoted region is not a placeholder. Panics if the number of placeholders does not match the number of arguments; use RawErr for a non-panicking variant.

func RawErr added in v0.5.0

func RawErr(sql string, args ...any) (Predicate, error)

RawErr is like Raw but returns an error wrapping ErrRawArgMismatch on a placeholder/argument-count mismatch instead of panicking. Use it when the raw SQL or its argument list is built dynamically and a mismatch is recoverable.

func True added in v0.5.0

func True() Predicate

True returns a no-op predicate that always matches. It is the identity for And and contributes nothing to a WHERE clause, so Where(True()) emits no WHERE at all. Use it as the "else" branch of conditional filtering.

func WhereIf added in v0.5.0

func WhereIf(cond bool, pred Predicate) Predicate

WhereIf returns pred when cond is true, otherwise a no-op (always-true) predicate that contributes nothing to the WHERE clause. It makes conditional filtering first-class instead of forcing callers to construct zero Predicate{} values or hand-rolled if/else chains around the builder.

func (Predicate) ToAST

func (p Predicate) ToAST() ast.WhereClause

type Publisher added in v0.6.0

type Publisher interface {
	Publish(ctx context.Context, msg ClaimedMessage) error
}

Publisher hands a claimed message to the transport. The host implements it. A nil error marks the message processed. An error records the failure and leaves the message for another attempt.

type PublisherFunc added in v0.6.0

type PublisherFunc func(ctx context.Context, msg ClaimedMessage) error

PublisherFunc adapts a function to the Publisher interface.

func (PublisherFunc) Publish added in v0.6.0

func (f PublisherFunc) Publish(ctx context.Context, msg ClaimedMessage) error

Publish calls f.

type QualifiedCol added in v0.5.0

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

QualifiedCol is a table-qualified column reference (table.column) used to build JOIN ON expressions and cross-table projections.

func QualifiedColRef added in v0.5.0

func QualifiedColRef(table, column string) QualifiedCol

QualifiedColRef creates a table-qualified column reference.

func (QualifiedCol) EqCol added in v0.5.0

func (q QualifiedCol) EqCol(other QualifiedCol) JoinOn

EqCol builds an equality ON expression between two qualified columns, with each identifier segment quoted (e.g. "categories"."name" = "products"."category").

func (QualifiedCol) Qualified added in v0.5.0

func (q QualifiedCol) Qualified() string

Qualified returns the raw "table.column" string.

func (QualifiedCol) Ref added in v0.5.0

func (q QualifiedCol) Ref() ColumnRef

Ref returns a ColumnRef whose name is the qualified "table.column" string, for use as a Select projection column. The dialect quotes each segment.

type QueryBuilder

type QueryBuilder[T any] struct {
	// contains filtered or unexported fields
}

QueryBuilder constructs and executes typed queries with an immutable builder pattern.

func (*QueryBuilder[T]) After added in v0.3.0

func (q *QueryBuilder[T]) After(cursor string) *QueryBuilder[T]

After positions a cursor-paginated query immediately past the row encoded by the cursor (as returned in a prior CursorPage.NextCursor). Combine with OrderBy and Take, then call Page.

func (*QueryBuilder[T]) All

func (q *QueryBuilder[T]) All(ctx context.Context) ([]*T, error)

All executes the query and returns all matching results.

func (*QueryBuilder[T]) AllRows added in v0.5.0

func (q *QueryBuilder[T]) AllRows() *QueryBuilder[T]

AllRows opts a BulkUpdate or BulkDelete out of the full-table safety guard, permitting a deliberate whole-table write with no Where predicate.

func (*QueryBuilder[T]) Before added in v0.5.0

func (q *QueryBuilder[T]) Before(cursor string) *QueryBuilder[T]

Before positions a cursor-paginated query immediately before the row encoded by the cursor, walking backward. Mutually exclusive with After. Combine with OrderBy and Take, then call Page; results are returned in natural (forward) order with PreviousCursor/HasPrev set for further backward navigation.

func (*QueryBuilder[T]) BulkDelete

func (q *QueryBuilder[T]) BulkDelete(ctx context.Context) (int, error)

BulkDelete deletes all rows matching the builder's WHERE conditions. If the model has soft delete, it performs a soft delete instead. Returns the number of affected rows.

func (*QueryBuilder[T]) BulkUpdate

func (q *QueryBuilder[T]) BulkUpdate(ctx context.Context, sets ...SetClause) (int, error)

BulkUpdate updates all rows matching the builder's WHERE conditions. Returns the number of affected rows.

func (*QueryBuilder[T]) Count

func (q *QueryBuilder[T]) Count(ctx context.Context) (int, error)

Count returns the number of matching rows.

func (*QueryBuilder[T]) Distinct added in v0.5.0

func (q *QueryBuilder[T]) Distinct() *QueryBuilder[T]

Distinct makes the projection emit SELECT DISTINCT, de-duplicating result rows. It affects Select and GroupBy projections; it has no effect on All/First/Count/Exists.

func (*QueryBuilder[T]) Exists

func (q *QueryBuilder[T]) Exists(ctx context.Context) (bool, error)

Exists returns true if at least one matching row exists.

func (*QueryBuilder[T]) First

func (q *QueryBuilder[T]) First(ctx context.Context) (*T, error)

First returns the first matching result or ErrNotFound if none exist.

func (*QueryBuilder[T]) FirstOrNil

func (q *QueryBuilder[T]) FirstOrNil(ctx context.Context) (*T, error)

FirstOrNil returns the first matching result or nil if none exist.

func (*QueryBuilder[T]) InnerJoin added in v0.5.0

func (q *QueryBuilder[T]) InnerJoin(table string, on JoinOn) *QueryBuilder[T]

InnerJoin adds an INNER JOIN to the given table on the supplied ON expression. See LeftJoin for column-qualification requirements.

func (*QueryBuilder[T]) LeftJoin added in v0.5.0

func (q *QueryBuilder[T]) LeftJoin(table string, on JoinOn) *QueryBuilder[T]

LeftJoin adds a LEFT JOIN to the given table on the supplied ON expression. Projection columns from the joined table must be table-qualified via QualifiedColRef(...).Ref(). Joins affect Select and GroupBy projections only.

func (*QueryBuilder[T]) Limit

func (q *QueryBuilder[T]) Limit(n int) *QueryBuilder[T]

Limit restricts the number of results returned.

func (*QueryBuilder[T]) OrderBy

func (q *QueryBuilder[T]) OrderBy(exprs ...OrderExpr) *QueryBuilder[T]

OrderBy sets the ordering for the query.

func (*QueryBuilder[T]) Page added in v0.3.0

func (q *QueryBuilder[T]) Page(ctx context.Context) (*CursorPage[T], error)

Page executes a keyset (cursor) page query. OrderBy is required and Take must be > 0. Any Skip/offset on the builder is ignored (keyset and OFFSET are mutually exclusive). When After is set the page begins past that cursor; when Before is set it walks backward and results are returned in natural order. Paging over a nullable ordering column requires NullsFirst()/NullsLast() on that column, otherwise Page returns ErrCursorColumnNullable.

func (*QueryBuilder[T]) PageOffset added in v0.3.0

func (q *QueryBuilder[T]) PageOffset(ctx context.Context) (*OffsetPage[T], error)

PageOffset executes an offset-based page query. Take (or Limit) sets the page size and Skip sets the offset. It runs a COUNT to populate Total/TotalPages.

func (*QueryBuilder[T]) Primary added in v0.3.0

func (q *QueryBuilder[T]) Primary() *QueryBuilder[T]

Primary forces this query to read from the primary connection instead of a read replica. Has no effect when no replicas are configured.

func (*QueryBuilder[T]) Skip

func (q *QueryBuilder[T]) Skip(n int) *QueryBuilder[T]

Skip sets the offset for the query (number of rows to skip).

func (*QueryBuilder[T]) Take added in v0.3.0

func (q *QueryBuilder[T]) Take(n int) *QueryBuilder[T]

Take limits the number of results returned. It is an alias for Limit that reads naturally alongside Skip/After in pagination expressions.

func (*QueryBuilder[T]) Timeout added in v0.5.0

func (q *QueryBuilder[T]) Timeout(d time.Duration) *QueryBuilder[T]

Timeout overrides the engine's default query timeout for this query only. The shorter of this value and any caller-supplied context deadline wins. Pass a non-positive duration to disable any default for this query.

func (*QueryBuilder[T]) Unscoped

func (q *QueryBuilder[T]) Unscoped() *QueryBuilder[T]

Unscoped returns a new builder with all global filters removed.

func (*QueryBuilder[T]) Where

func (q *QueryBuilder[T]) Where(pred Predicate) *QueryBuilder[T]

Where adds a filter predicate. Multiple calls are ANDed together.

func (*QueryBuilder[T]) WithoutFilter

func (q *QueryBuilder[T]) WithoutFilter(name string) *QueryBuilder[T]

WithoutFilter returns a new builder with the named filter removed.

type QueryEvent

type QueryEvent struct {
	SQL      string
	Args     []any
	Duration time.Duration
	Err      error
}

QueryEvent contains information about an executed query.

type QueryHook

type QueryHook func(ctx context.Context, event QueryEvent)

QueryHook is called after every query execution.

type RawExpr

type RawExpr struct {
	SQL string
}

RawExpr represents a raw SQL expression to embed in a query (e.g., NOW()).

type Relation

type Relation[T any] struct {
	// contains filtered or unexported fields
}

func NewRelation

func NewRelation[T any](name string) Relation[T]

func (Relation[T]) Name

func (r Relation[T]) Name() string

type RelationInfo

type RelationInfo struct {
	Name        string
	Type        RelationType
	FKColumn    string
	JoinTable   string // pivot table name (many-to-many only)
	RefColumn   string // target FK column in pivot table (many-to-many only)
	RelatedMeta *ModelMetaBase
	FieldSetter func(parent any, related any)
}

RelationInfo describes a relationship between two models.

type RelationType

type RelationType int

RelationType describes the kind of relationship between two models.

const (
	// HasMany indicates the parent has many related entities (1:N).
	HasMany RelationType = iota
	// HasOne indicates the parent has one related entity (1:1).
	HasOne
	// BelongsTo indicates the entity references a parent via a foreign key.
	BelongsTo
	// ManyToMany indicates a many-to-many relationship via a pivot table.
	ManyToMany
)

type Relay added in v0.6.0

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

Relay publishes outbox messages to a transport and marks them processed.

More than one replica can run a Relay against one table. A claim takes a lease on a batch of messages, so a second replica skips the messages that the first one holds. Postgres claims with FOR UPDATE SKIP LOCKED. SQLite and LibSQL claim with a conditional UPDATE, which is safe because SQLite serialises writers.

Delivery is at-least-once, not exactly-once. A publish that outlives its lease can run a second time on another replica. Make the transport or the consumer idempotent.

Every replica must run a synchronised clock. The lease compares the times that the replicas write.

func NewRelay added in v0.6.0

func NewRelay(e *Engine, table string, pub Publisher, opts ...RelayOption) *Relay

NewRelay creates a relay for one outbox table. The table must carry the lease columns that OutboxSchema emits.

func (*Relay) Run added in v0.6.0

func (r *Relay) Run(ctx context.Context) error

Run polls the outbox until ctx ends. It returns the context error.

Run polls again at once after a full batch, and it waits for the poll interval after a short batch. A claim or publish error does not stop the loop. Run reports the error through the engine's error hooks and continues.

func (*Relay) RunOnce added in v0.6.0

func (r *Relay) RunOnce(ctx context.Context) (int, error)

RunOnce claims one batch, publishes it, and returns how many messages it published. A message that fails to publish is not counted.

type RelayOption added in v0.6.0

type RelayOption func(*relayConfig)

RelayOption configures a Relay.

func WithRelayBatchSize added in v0.6.0

func WithRelayBatchSize(n int) RelayOption

WithRelayBatchSize sets how many messages one claim takes. The default is 100.

func WithRelayConcurrency added in v0.6.0

func WithRelayConcurrency(n int) RelayOption

WithRelayConcurrency limits how many partitions publish at the same time. The default is 8.

func WithRelayLease added in v0.6.0

func WithRelayLease(d time.Duration) RelayOption

WithRelayLease sets how long a claim holds a message. Another replica can claim the message again after the lease ends. Set it well above the time one publish takes. The default is 30 seconds.

func WithRelayMaxAttempts added in v0.6.0

func WithRelayMaxAttempts(n int) RelayOption

WithRelayMaxAttempts sets how many claims a message gets before the relay marks it dead. The default is 5. A value below 1 disables the dead-letter path, and the relay retries forever.

func WithRelayOnDead added in v0.6.0

func WithRelayOnDead(fn func(ctx context.Context, msg ClaimedMessage, err error)) RelayOption

WithRelayOnDead registers a callback that runs one time for each message that reaches the attempt limit. Use it to alert a person. The callback must not block for long.

func WithRelayOnError added in v0.6.0

func WithRelayOnError(fn func(ctx context.Context, err error)) RelayOption

WithRelayOnError registers a callback for an error that Run cannot return, because Run only ends when the context ends. Without this option Run logs the error through the engine logger at error level. Register it in production, so that a claim failure or a publish failure reaches your alerting.

func WithRelayPollInterval added in v0.6.0

func WithRelayPollInterval(d time.Duration) RelayOption

WithRelayPollInterval sets the wait between two empty polls. The default is one second. Run polls again at once after a full batch.

func WithRelayWorkerID added in v0.6.0

func WithRelayWorkerID(id string) RelayOption

WithRelayWorkerID names this replica in the claimed_by column. The default is the host name and the process ID.

type Repository

type Repository[T any] struct {
	// contains filtered or unexported fields
}

Repository provides typed query access for a specific model.

func NewRepository

func NewRepository[T any](engine *Engine, meta ModelMeta[T]) *Repository[T]

NewRepository creates a new Repository for the given model metadata.

func (*Repository[T]) After added in v0.3.0

func (r *Repository[T]) After(cursor string) *QueryBuilder[T]

After starts a cursor-paginated query positioned past the given cursor.

func (*Repository[T]) All

func (r *Repository[T]) All(ctx context.Context) ([]*T, error)

All returns all records for this model.

func (*Repository[T]) AllRows added in v0.5.0

func (r *Repository[T]) AllRows() *QueryBuilder[T]

AllRows opts a BulkUpdate or BulkDelete started from this repository out of the full-table safety guard, permitting a deliberate whole-table write with no Where predicate.

func (*Repository[T]) Before added in v0.5.0

func (r *Repository[T]) Before(cursor string) *QueryBuilder[T]

Before starts a cursor-paginated query positioned before the given cursor (backward).

func (*Repository[T]) BulkInsert

func (r *Repository[T]) BulkInsert(ctx context.Context, entities []*T) (int, error)

BulkInsert inserts multiple entities in batches, bypassing change tracking. Returns the total number of rows affected.

IMPORTANT: BulkInsert is a fast path that opens its own transaction and does NOT go through the change tracker. Domain events recorded on the entities (RecordEvent) are NOT collected, NOT written to the outbox, and NOT dispatched to before/after-commit hooks. Use SaveChanges (Engine.Transaction or a UnitOfWork) when you need event dispatch, or BulkInsertWithEvents to persist events through the outbox path inside the bulk transaction.

func (*Repository[T]) BulkInsertWithEvents added in v0.5.0

func (r *Repository[T]) BulkInsertWithEvents(ctx context.Context, entities []*T) (int, error)

BulkInsertWithEvents inserts multiple entities in batches like BulkInsert, but additionally collects domain events recorded on the entities (RecordEvent), runs the engine's registered OnBeforeCommit hooks (e.g. the outbox writer from UseOutbox) inside the same transaction so event persistence commits atomically with the inserted rows, and dispatches the events to after-commit hooks once the transaction commits. Change tracking is still bypassed; this is a targeted opt-in for event delivery on the bulk path.

func (*Repository[T]) BulkUpsert

func (r *Repository[T]) BulkUpsert(ctx context.Context, entities []*T, opts ...UpsertOption) (int, error)

func (*Repository[T]) Count

func (r *Repository[T]) Count(ctx context.Context) (int, error)

Count returns the total number of records for this model.

func (*Repository[T]) Distinct added in v0.5.0

func (r *Repository[T]) Distinct() *QueryBuilder[T]

Distinct returns a query builder with SELECT DISTINCT enabled.

func (*Repository[T]) Exists

func (r *Repository[T]) Exists(ctx context.Context) (bool, error)

Exists returns true if any records exist for this model.

func (*Repository[T]) Find

func (r *Repository[T]) Find(ctx context.Context, id any) (*T, error)

Find looks up a single record by its primary key. For a composite key, id is the model's key struct.

func (*Repository[T]) First

func (r *Repository[T]) First(ctx context.Context) (*T, error)

First returns the first record or ErrNotFound.

func (*Repository[T]) FirstOrNil

func (r *Repository[T]) FirstOrNil(ctx context.Context) (*T, error)

FirstOrNil returns the first record or nil.

func (*Repository[T]) Include

func (r *Repository[T]) Include(rels ...IncludeSpec) *IncludableQuery[T]

Include begins a query that will eagerly load the given relationships.

func (*Repository[T]) InnerJoin added in v0.5.0

func (r *Repository[T]) InnerJoin(table string, on JoinOn) *QueryBuilder[T]

InnerJoin returns a query builder with an INNER JOIN added.

func (*Repository[T]) LeftJoin added in v0.5.0

func (r *Repository[T]) LeftJoin(table string, on JoinOn) *QueryBuilder[T]

LeftJoin returns a query builder with a LEFT JOIN added.

func (*Repository[T]) Limit

func (r *Repository[T]) Limit(n int) *QueryBuilder[T]

Limit starts a query with a row limit.

func (*Repository[T]) OrderBy

func (r *Repository[T]) OrderBy(exprs ...OrderExpr) *QueryBuilder[T]

OrderBy starts an ordered query.

func (*Repository[T]) Primary added in v0.3.0

func (r *Repository[T]) Primary() *QueryBuilder[T]

Primary returns a query builder that reads from the primary connection, bypassing read replicas (useful for read-your-writes consistency).

func (*Repository[T]) Skip added in v0.3.0

func (r *Repository[T]) Skip(n int) *QueryBuilder[T]

Skip starts a query with a row offset.

func (*Repository[T]) Take added in v0.3.0

func (r *Repository[T]) Take(n int) *QueryBuilder[T]

Take starts a query with a row limit (alias for Limit, reads naturally with pagination).

func (*Repository[T]) Unscoped

func (r *Repository[T]) Unscoped() *QueryBuilder[T]

Unscoped returns a query builder with all global filters removed.

func (*Repository[T]) Where

func (r *Repository[T]) Where(pred Predicate) *QueryBuilder[T]

Where starts a filtered query.

func (*Repository[T]) WithoutFilter

func (r *Repository[T]) WithoutFilter(name string) *QueryBuilder[T]

WithoutFilter returns a query builder with the named filter removed.

type RetryConfig added in v0.5.0

type RetryConfig struct {
	// MaxAttempts is the total number of tries, including the first. <=0 means 3.
	MaxAttempts int
	// BaseDelay is the first backoff delay; subsequent delays grow exponentially.
	// <=0 means 50ms.
	BaseDelay time.Duration
	// MaxDelay caps the backoff delay. <=0 means 1s.
	MaxDelay time.Duration
}

RetryConfig bounds serialization-failure retries for TransactionWithRetry. Zero fields fall back to sensible defaults (3 attempts, 50ms base delay growing exponentially with jitter, capped at 1s).

type Row

type Row interface {
	Scan(dest ...any) error
}

Row represents a single database row that can scan values into destinations.

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Close()
	Err() error
}

Rows represents a set of database rows from a query result.

type SetClause

type SetClause struct {
	Column string
	Value  any
}

SetClause pairs a column name with a value for use in bulk updates and upserts.

func Set

func Set[C interface{ Name() string }, V any](col C, value V) SetClause

Set creates a SetClause from a typed column and value.

type SoftDelete

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

func (SoftDelete) DeletedAt

func (s SoftDelete) DeletedAt() *time.Time

func (*SoftDelete) DeletedAtPtr

func (s *SoftDelete) DeletedAtPtr() **time.Time

func (SoftDelete) IsDeleted

func (s SoftDelete) IsDeleted() bool

type Span added in v0.3.0

type Span interface {
	End()
	RecordError(err error)
}

Span is a minimal tracing span. It is satisfied by a thin adapter over any tracing library (e.g. OpenTelemetry), keeping drel free of tracing dependencies. End is called when the traced operation completes; RecordError attaches an error to the span (a nil error is ignored).

type StringColumn

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

func NewStringCol

func NewStringCol(name string) StringColumn

func (StringColumn) Asc

func (c StringColumn) Asc() OrderExpr

func (StringColumn) ColRef added in v0.3.0

func (c StringColumn) ColRef() ColumnRef

ColRef returns a ColumnRef for use with Select projections and aggregates.

func (StringColumn) Contains

func (c StringColumn) Contains(substr string) Predicate

func (StringColumn) Desc

func (c StringColumn) Desc() OrderExpr

func (StringColumn) Eq

func (c StringColumn) Eq(v string) Predicate

func (StringColumn) GT

func (c StringColumn) GT(v string) Predicate

func (StringColumn) GTE

func (c StringColumn) GTE(v string) Predicate

func (StringColumn) HasPrefix

func (c StringColumn) HasPrefix(prefix string) Predicate

func (StringColumn) HasSuffix

func (c StringColumn) HasSuffix(suffix string) Predicate

func (StringColumn) ILike

func (c StringColumn) ILike(pattern string) Predicate

func (StringColumn) In

func (c StringColumn) In(vs ...string) Predicate

func (StringColumn) IsNotNull

func (c StringColumn) IsNotNull() Predicate

func (StringColumn) IsNull

func (c StringColumn) IsNull() Predicate

func (StringColumn) LT

func (c StringColumn) LT(v string) Predicate

func (StringColumn) LTE

func (c StringColumn) LTE(v string) Predicate

func (StringColumn) Like

func (c StringColumn) Like(pattern string) Predicate

func (StringColumn) NEQ

func (c StringColumn) NEQ(v string) Predicate

func (StringColumn) Name

func (c StringColumn) Name() string

func (StringColumn) NotIn

func (c StringColumn) NotIn(vs ...string) Predicate

type TimeColumn added in v0.5.0

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

TimeColumn is a dedicated column type for time.Time values. It provides range operators (GT/GTE/LT/LTE/Between/Before/After) in addition to the standard equality/null/in operators available on Column[T].

func NewTimeCol added in v0.5.0

func NewTimeCol(name string) TimeColumn

func (TimeColumn) After added in v0.5.0

func (c TimeColumn) After(t time.Time) Predicate

After is a convenience alias for GT.

func (TimeColumn) Asc added in v0.5.0

func (c TimeColumn) Asc() OrderExpr

func (TimeColumn) Before added in v0.5.0

func (c TimeColumn) Before(t time.Time) Predicate

Before is a convenience alias for LT.

func (TimeColumn) Between added in v0.5.0

func (c TimeColumn) Between(low, high time.Time) Predicate

Between returns a predicate matching rows where the column value is between low and high inclusive (SQL BETWEEN low AND high).

func (TimeColumn) ColRef added in v0.5.0

func (c TimeColumn) ColRef() ColumnRef

func (TimeColumn) Desc added in v0.5.0

func (c TimeColumn) Desc() OrderExpr

func (TimeColumn) Eq added in v0.5.0

func (c TimeColumn) Eq(v time.Time) Predicate

func (TimeColumn) GT added in v0.5.0

func (c TimeColumn) GT(v time.Time) Predicate

func (TimeColumn) GTE added in v0.5.0

func (c TimeColumn) GTE(v time.Time) Predicate

func (TimeColumn) In added in v0.5.0

func (c TimeColumn) In(vs ...time.Time) Predicate

func (TimeColumn) IsNotNull added in v0.5.0

func (c TimeColumn) IsNotNull() Predicate

func (TimeColumn) IsNull added in v0.5.0

func (c TimeColumn) IsNull() Predicate

func (TimeColumn) LT added in v0.5.0

func (c TimeColumn) LT(v time.Time) Predicate

func (TimeColumn) LTE added in v0.5.0

func (c TimeColumn) LTE(v time.Time) Predicate

func (TimeColumn) NEQ added in v0.5.0

func (c TimeColumn) NEQ(v time.Time) Predicate

func (TimeColumn) Name added in v0.5.0

func (c TimeColumn) Name() string

func (TimeColumn) NotIn added in v0.5.0

func (c TimeColumn) NotIn(vs ...time.Time) Predicate

type Tracer added in v0.3.0

type Tracer interface {
	// Start begins a span named name and returns a context carrying it.
	Start(ctx context.Context, name string) (context.Context, Span)
}

Tracer starts spans for drel operations. Provide one via WithTracer to emit a span per executed query. A nil Tracer disables tracing.

type Tx

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

Tx represents an active database transaction with change tracking.

func FromContext added in v0.6.0

func FromContext(ctx context.Context) (*Tx, bool)

FromContext returns the transaction that WithTx put in ctx. The second result is false when no transaction is present.

func MustFromContext added in v0.6.0

func MustFromContext(ctx context.Context) *Tx

MustFromContext returns the transaction that WithTx put in ctx. It panics when no transaction is present. Use it in code that a transaction must always surround, so that a wiring fault fails at once.

func (*Tx) AdvisoryLock added in v0.5.0

func (tx *Tx) AdvisoryLock(ctx context.Context, key int64) error

AdvisoryLock acquires a Postgres transaction-scoped advisory lock for key, blocking until it is granted. The lock is released automatically when the transaction commits or rolls back. On SQLite this is a documented no-op (returns nil) because SQLite serializes writers at the database level and has no advisory-lock primitive. Must be called within a transaction.

func (*Tx) Exec

func (tx *Tx) Exec(ctx context.Context, sql string, args ...any) (int64, error)

Exec executes a raw SQL statement within the transaction. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL).

func (*Tx) HardRemove

func (tx *Tx) HardRemove(entity any) error

HardRemove marks a tracked entity for permanent (hard) deletion on the next flush, bypassing soft delete even when the model supports it.

func (*Tx) NewBatch added in v0.5.0

func (tx *Tx) NewBatch() *Batch

NewBatch creates a query batch that runs on this transaction's connection. Batched queries observe the transaction's own uncommitted writes. Because a pgx pipeline cannot span a transaction connection in the general case, a transaction batch always runs sequentially (no network pipelining). Batch results are read-only: they are not snapshotted into the change tracker.

func (*Tx) Query added in v0.5.0

func (tx *Tx) Query(ctx context.Context, sql string, args ...any) (Rows, error)

Query executes a raw SQL query within the transaction and returns the result rows. The caller must close the returned Rows when done. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL). It mirrors Engine.Query so the raw escape hatch is identical inside and outside a transaction.

func (*Tx) QueryRow

func (tx *Tx) QueryRow(ctx context.Context, sql string, args ...any) Row

QueryRow executes a raw SQL query within the transaction that returns at most one row. $N placeholders are rewritten to ? on dialects that use ? (SQLite/libSQL).

func (*Tx) SaveChanges

func (tx *Tx) SaveChanges(ctx context.Context) error

SaveChanges flushes all tracked changes within this transaction.

func (*Tx) Savepoint added in v0.3.0

func (tx *Tx) Savepoint(ctx context.Context, name string, fn func(sp *Tx) error) error

Savepoint runs fn within a nested SAVEPOINT. If fn returns nil the savepoint is released; otherwise the transaction is rolled back to the savepoint and the change tracker is reverted to its state before the savepoint, so entities added inside fn are dropped and prior entities keep their earlier state. The outer transaction continues either way. Savepoints may be nested, including reusing the same name.

Note: rollback reverts the change tracker, not the in-memory Go structs. If a flush inside fn populated generated fields on an entity (id, version, created_at) and the savepoint is then rolled back, that entity still carries those values in memory even though its row no longer exists. Discard such entities rather than reusing them after a rolled-back savepoint.

func (*Tx) TryAdvisoryLock added in v0.5.0

func (tx *Tx) TryAdvisoryLock(ctx context.Context, key int64) (bool, error)

TryAdvisoryLock attempts to acquire a Postgres transaction-scoped advisory lock for key without blocking, reporting whether it was acquired. The lock is released automatically when the transaction commits or rolls back. On SQLite this is a documented no-op and always returns (true, nil). Must be called within a transaction.

type TxIncludableQuery added in v0.5.0

type TxIncludableQuery[T any] struct {
	// contains filtered or unexported fields
}

TxIncludableQuery eagerly loads relationships within an explicit transaction. Root reads and all split sub-queries run on the transaction connection, and loaded children are tracked by the transaction's change tracker (unless the root builder is AsNoTracking) so their edits are flushed on commit.

func (*TxIncludableQuery[T]) All added in v0.5.0

func (q *TxIncludableQuery[T]) All(ctx context.Context) ([]*T, error)

All returns all matching records and loads included relationships.

func (*TxIncludableQuery[T]) Find added in v0.5.0

func (q *TxIncludableQuery[T]) Find(ctx context.Context, id any) (*T, error)

Find looks up a single record by primary key and loads included relationships.

func (*TxIncludableQuery[T]) First added in v0.5.0

func (q *TxIncludableQuery[T]) First(ctx context.Context) (*T, error)

First returns the first matching record and loads included relationships.

func (*TxIncludableQuery[T]) Include added in v0.5.0

func (q *TxIncludableQuery[T]) Include(rels ...IncludeSpec) *TxIncludableQuery[T]

Include adds more relationships to eagerly load.

func (*TxIncludableQuery[T]) Limit added in v0.5.0

func (q *TxIncludableQuery[T]) Limit(n int) *TxIncludableQuery[T]

Limit restricts the number of root records returned.

func (*TxIncludableQuery[T]) OrderBy added in v0.5.0

func (q *TxIncludableQuery[T]) OrderBy(exprs ...OrderExpr) *TxIncludableQuery[T]

OrderBy sets the ordering for the root query.

func (*TxIncludableQuery[T]) Where added in v0.5.0

func (q *TxIncludableQuery[T]) Where(pred Predicate) *TxIncludableQuery[T]

Where adds a filter predicate to the root query.

type TxOption

type TxOption func(*txConfig)

TxOption configures transaction behaviour.

func WithIsolation

func WithIsolation(level IsolationLevel) TxOption

WithIsolation sets the isolation level for the transaction.

func WithReadOnly added in v0.5.0

func WithReadOnly() TxOption

WithReadOnly begins the transaction in read-only mode. On Postgres this lets the server skip snapshot bookkeeping and rejects any write inside the tx; on SQLite/libSQL the flag is forwarded to the driver. Compose it with WithIsolation; both reach the driver's BeginTx.

func WithRetry added in v0.5.0

func WithRetry(cfg RetryConfig) TxOption

WithRetry configures TransactionWithRetry to re-run the transaction on ErrSerializationFailure using the given bounds. It is read only by TransactionWithRetry; plain Transaction ignores it.

type TxQueryBuilder

type TxQueryBuilder[T any] struct {
	// contains filtered or unexported fields
}

TxQueryBuilder constructs and executes typed queries within a transaction. By default, results are tracked by the transaction's change tracker so that mutations are detected on SaveChanges; use AsNoTracking to opt out.

func (*TxQueryBuilder[T]) After added in v0.3.0

func (q *TxQueryBuilder[T]) After(cursor string) *TxQueryBuilder[T]

After positions a cursor-paginated query past the row encoded by the cursor.

func (*TxQueryBuilder[T]) All

func (q *TxQueryBuilder[T]) All(ctx context.Context) ([]*T, error)

All executes the query and returns all matching results.

func (*TxQueryBuilder[T]) AsNoTracking added in v0.3.0

func (q *TxQueryBuilder[T]) AsNoTracking() *TxQueryBuilder[T]

AsNoTracking returns a copy of the builder whose results will not be tracked.

func (*TxQueryBuilder[T]) Before added in v0.5.0

func (q *TxQueryBuilder[T]) Before(cursor string) *TxQueryBuilder[T]

Before positions a cursor-paginated query before the given cursor (backward).

func (*TxQueryBuilder[T]) BulkDelete added in v0.5.0

func (q *TxQueryBuilder[T]) BulkDelete(ctx context.Context) (int, error)

BulkDelete deletes all rows matching the tx builder's WHERE conditions on the transaction connection. Soft-delete models are soft-deleted. Returns the number of affected rows.

func (*TxQueryBuilder[T]) BulkUpdate added in v0.5.0

func (q *TxQueryBuilder[T]) BulkUpdate(ctx context.Context, sets ...SetClause) (int, error)

BulkUpdate updates all rows matching the tx builder's WHERE conditions on the transaction connection. Returns the number of affected rows.

func (*TxQueryBuilder[T]) Count

func (q *TxQueryBuilder[T]) Count(ctx context.Context) (int, error)

Count returns the number of matching rows.

func (*TxQueryBuilder[T]) Exists

func (q *TxQueryBuilder[T]) Exists(ctx context.Context) (bool, error)

Exists returns true if at least one matching row exists.

func (*TxQueryBuilder[T]) First

func (q *TxQueryBuilder[T]) First(ctx context.Context) (*T, error)

First returns the first matching result or ErrNotFound if none exist.

func (*TxQueryBuilder[T]) FirstOrNil

func (q *TxQueryBuilder[T]) FirstOrNil(ctx context.Context) (*T, error)

FirstOrNil returns the first matching result or nil if none exist.

func (*TxQueryBuilder[T]) Limit

func (q *TxQueryBuilder[T]) Limit(n int) *TxQueryBuilder[T]

Limit restricts the number of results returned.

func (*TxQueryBuilder[T]) OrderBy

func (q *TxQueryBuilder[T]) OrderBy(exprs ...OrderExpr) *TxQueryBuilder[T]

OrderBy sets the ordering for the query.

func (*TxQueryBuilder[T]) Page added in v0.3.0

func (q *TxQueryBuilder[T]) Page(ctx context.Context) (*CursorPage[T], error)

Page executes a keyset (cursor) page query within the transaction.

func (*TxQueryBuilder[T]) PageOffset added in v0.3.0

func (q *TxQueryBuilder[T]) PageOffset(ctx context.Context) (*OffsetPage[T], error)

PageOffset executes an offset-based page query within the transaction.

func (*TxQueryBuilder[T]) Skip

func (q *TxQueryBuilder[T]) Skip(n int) *TxQueryBuilder[T]

Skip sets the offset for the query (number of rows to skip).

func (*TxQueryBuilder[T]) Take added in v0.3.0

func (q *TxQueryBuilder[T]) Take(n int) *TxQueryBuilder[T]

Take limits the number of results returned. Alias for Limit.

func (*TxQueryBuilder[T]) Unscoped

func (q *TxQueryBuilder[T]) Unscoped() *TxQueryBuilder[T]

Unscoped returns a new builder with all global filters removed.

func (*TxQueryBuilder[T]) Where

func (q *TxQueryBuilder[T]) Where(pred Predicate) *TxQueryBuilder[T]

Where adds a filter predicate. Multiple calls are ANDed together.

func (*TxQueryBuilder[T]) WithoutFilter

func (q *TxQueryBuilder[T]) WithoutFilter(name string) *TxQueryBuilder[T]

WithoutFilter returns a new builder with the named filter removed.

type TxRepository

type TxRepository[T any] struct {
	// contains filtered or unexported fields
}

TxRepository provides tracked query and mutation access within a transaction.

func NewTxRepository

func NewTxRepository[T any](tx *Tx, meta ModelMeta[T]) *TxRepository[T]

NewTxRepository creates a new TxRepository for the given transaction and model metadata.

func Repo added in v0.4.0

func Repo[T any](tx *Tx, meta ModelMeta[T]) *TxRepository[T]

Repo returns a transaction-bound repository for the given model metadata. It is sugar for NewTxRepository(tx, meta). (A method form is impossible in Go because methods cannot have their own type parameters.)

func (*TxRepository[T]) Add

func (r *TxRepository[T]) Add(entity *T)

Add marks an entity for insertion on the next flush.

func (*TxRepository[T]) All

func (r *TxRepository[T]) All(ctx context.Context) ([]*T, error)

All returns all records for this model within the transaction, tracking them.

func (*TxRepository[T]) AsNoTracking added in v0.3.0

func (r *TxRepository[T]) AsNoTracking() *TxQueryBuilder[T]

AsNoTracking returns a query builder whose results are not tracked, for read-only queries within the transaction.

func (*TxRepository[T]) Attach added in v0.3.0

func (r *TxRepository[T]) Attach(entity *T, state EntityState)

Attach begins tracking an externally-constructed entity (e.g. deserialized from a request) in the given state. StateModified flushes a full-column UPDATE; StateAdded behaves like Add; StateUnchanged snapshots the entity so subsequent mutations are detected.

func (*TxRepository[T]) BulkInsert added in v0.5.0

func (r *TxRepository[T]) BulkInsert(ctx context.Context, entities []*T) (int, error)

BulkInsert inserts multiple entities in batches on the transaction connection, bypassing change tracking. The inserts participate in the surrounding transaction and roll back with it on failure.

func (*TxRepository[T]) BulkUpsert added in v0.5.0

func (r *TxRepository[T]) BulkUpsert(ctx context.Context, entities []*T, opts ...UpsertOption) (int, error)

BulkUpsert inserts or updates multiple entities on the transaction connection, participating in the surrounding transaction.

func (*TxRepository[T]) Detach added in v0.3.0

func (r *TxRepository[T]) Detach(entity *T)

Detach stops tracking an entity so its mutations are no longer flushed.

func (*TxRepository[T]) Find

func (r *TxRepository[T]) Find(ctx context.Context, id any) (*T, error)

Find looks up a single record by primary key and begins tracking it. For a composite key, id is the model's key struct.

func (*TxRepository[T]) Include added in v0.5.0

func (r *TxRepository[T]) Include(rels ...IncludeSpec) *TxIncludableQuery[T]

Include begins a tracked query that eagerly loads the given relationships within the transaction. Root reads and all split sub-queries run on the transaction connection, observing the transaction's own uncommitted writes.

func (*TxRepository[T]) OrderBy added in v0.3.0

func (r *TxRepository[T]) OrderBy(exprs ...OrderExpr) *TxQueryBuilder[T]

OrderBy starts an ordered, tracked query within the transaction.

func (*TxRepository[T]) Remove

func (r *TxRepository[T]) Remove(entity *T) error

Remove marks a tracked entity for deletion on the next flush.

func (*TxRepository[T]) Unscoped

func (r *TxRepository[T]) Unscoped() *TxQueryBuilder[T]

Unscoped returns a query builder with all global filters removed.

func (*TxRepository[T]) Where

func (r *TxRepository[T]) Where(pred Predicate) *TxQueryBuilder[T]

Where starts a filtered, tracked query within the transaction.

func (*TxRepository[T]) WithoutFilter

func (r *TxRepository[T]) WithoutFilter(name string) *TxQueryBuilder[T]

WithoutFilter returns a query builder with the named filter removed.

type UpsertOption

type UpsertOption func(*upsertConfig)

UpsertOption configures BulkUpsert behavior.

func ConflictColumns

func ConflictColumns[C interface{ Name() string }](cols ...C) UpsertOption

ConflictColumns specifies which columns form the conflict target for an upsert.

func DoNothing added in v0.5.0

func DoNothing() UpsertOption

DoNothing configures the upsert to emit ON CONFLICT (...) DO NOTHING: rows that conflict on the conflict target are skipped instead of updated. When set, UpdateOnConflict becomes optional and is ignored.

func UpdateOnConflict

func UpdateOnConflict[C interface{ Name() string }](cols ...C) UpsertOption

UpdateOnConflict specifies which columns to update when a conflict is detected.

type Versioned

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

func (Versioned) Version

func (v Versioned) Version() int

func (*Versioned) VersionPtr

func (v *Versioned) VersionPtr() *int

Directories

Path Synopsis
cmd
drel command
Package es holds an append-only event store.
Package es holds an append-only event store.
examples
api command
Example: api
Example: api
bulk-ops command
Example: bulk-ops
Example: bulk-ops
composite-keys command
Example: composite-keys
Example: composite-keys
enums command
Example: enums
Example: enums
event-sourcing command
Example: event-sourcing
Example: event-sourcing
feature-slices command
Example: feature-slices
Example: feature-slices
getting-started command
Example: getting-started
Example: getting-started
inbox command
Example: inbox
Example: inbox
internals command
Example: internals
Example: internals
model-features command
Example: model-features
Example: model-features
multi-model command
Example: multi-model
Example: multi-model
observability command
Example: observability
Example: observability
outbox command
Example: outbox
Example: outbox
relationships command
Example: relationships
Example: relationships
sqlite-todo command
Example: sqlite-todo
Example: sqlite-todo
uuid-keys command
Example: uuid-keys
Example: uuid-keys
value-objects command
internal
ast
dberr
Package dberr classifies driver-specific database errors into a small set of dialect-neutral sentinel errors that callers can match with errors.Is, without importing pgconn or the sqlite driver themselves.
Package dberr classifies driver-specific database errors into a small set of dialect-neutral sentinel errors that callers can match with errors.Is, without importing pgconn or the sqlite driver themselves.
driver/libsqldriver
Package libsqldriver implements driver.Driver for libSQL / Turso using the database/sql libsql driver.
Package libsqldriver implements driver.Driver for libSQL / Turso using the database/sql libsql driver.
dsn
Package dsn centralises DSN dialect detection, driver opening, and auth-token injection so the runtime engine and the migrate CLI share one implementation and cannot drift apart.
Package dsn centralises DSN dialect detection, driver opening, and auth-token injection so the runtime engine and the migrate CLI share one implementation and cannot drift apart.

Jump to

Keyboard shortcuts

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