Documentation
¶
Overview ¶
Package r3 provides a universal CRUD repository abstraction for Go.
The core interface is CRUD, a generic interface parameterized by entity type T and primary key type ID. It composes Querier (Get, List, Count) and Commander (Create, Update, Patch, Delete) — use the narrower sub-interfaces when full CRUD access is not needed (e.g. read-only config stores only need Querier).
Queries are built with composable value types — Filters, Sorts, PaginationSpec, CursorSpec, Fields, and Preloads — combined into a single Query struct. Queries are immutable; Query.MergeWith returns a new value, making it safe to combine queries from different sources (defaults, user request, permission scope). Build filters with the short-form helpers (Eq, Gt, In, Like, Between, ...) for the common case, or the FieldSpec-based forms (F, Fop) when you need table hints or nested paths.
Schema and capabilities ¶
SchemaOf reflects an entity's struct tags into a Schema — an ordered set of capability-bearing [Attribute]s. Each attribute declares what it may do via five capabilities (Filterable, Sortable, Queryable, Creatable, Mutable); defaults are permissive (a plain scalar gets all five) and tags only tighten them (no-filter, no-sort, no-output, readonly, immutable, enum). The PK, the created_at/updated_at timestamps, and the soft-delete column are read-only by default.
Schema.ValidateQuery turns an unknown or disallowed filter/sort/select field into a typed error (ErrUnknownField, ErrFieldNotFilterable, ErrFieldNotSortable, ErrFieldNotQueryable) before any SQL is built, and the SQL engine shapes writes to honor Creatable/Mutable — a full Update can no longer clobber created_at or resurrect a soft-deleted row. The created_at and updated_at columns are system-managed: read-only to callers, but stamped with server time by the engine (created_at on create, updated_at on every write). Capabilities are the public ceiling; the permissions feature only narrows them per-actor.
An audited system/worker door writes a user-immutable column explicitly: WithoutWriteGuard on the context, or the SystemWriter wrapper. It skips only the capability check — the structural floor (computed attributes and the PK are never writable) holds, and history/metrics still record the write.
Errors and pagination ¶
Get normalizes every backend's "not found" condition to the ErrNotFound sentinel, so callers detect a missing record with errors.Is the same way regardless of driver. List paginates by default (PageSizeDefault items); pass Unpaginated to opt out, or compare the returned total against the slice length to detect truncation. Count answers "how many match?" without materializing rows — only Filters and IncludeTrashed affect its result.
Project layout ¶
The project is organized in five layers, each depending only on the layers above:
- r3 (this package): Interfaces and query model. Zero external dependencies.
- dialects/: Stateless converters between r3 types and format-specific representations. Two categories: data-store (sql, bson) and serialization (json, yaml, toml, url, schema). No I/O.
- engine/: Complete CRUD implementations per storage category (sql, mongo, file). Contains reflection, query building, and execution logic.
- drivers/: Ready-to-use constructors for specific libraries. Raw SQL drivers (pq, pgx, mysql, sqlite3) embed engine/sql.BaseCRUD. ORM drivers (gorm, bun, gopg) use their own ORM API but share engine/sql.PreparedListQuery for filter/sort/pagination translation. The mongo driver wraps engine/mongo.
- features/: Composable decorators (permissions, history, metrics, validation, softdelete, transactor) that wrap any r3.CRUD regardless of backend.
Key design principle ¶
Features compose across backends. The same permission checker, audit log, or metrics collector works whether the underlying repo is PostgreSQL, MongoDB, or a YAML file. This makes r3 particularly useful in systems that use multiple data sources — the behavioral layer is written once and applied everywhere.
Index ¶
- Constants
- Variables
- func As[C any, T any, ID comparable](repo CRUD[T, ID]) (C, bool)
- func EncodeCursor(cv CursorValues) (string, error)
- func ExtractBetweenBounds(value any) (any, any, error)
- func FieldsToStrings(fields Fields) []string
- func FinalizeCount[T any](entities []T, paginatedCount int64, isPaginated bool) ([]T, int64)
- func FinalizeCountCursor[T any](entities []T) ([]T, int64)
- func InTx[T any, ID comparable](ctx context.Context, repo CRUD[T, ID], fn func(tx CRUD[T, ID]) error) error
- func SupportsTx[T any, ID comparable](repo CRUD[T, ID]) bool
- func ValidateIdentifier(s string) error
- func WithActor(ctx context.Context, actor Actor) context.Context
- func WithoutWriteGuard(ctx context.Context) context.Context
- func WriteGuardBypassed(ctx context.Context) bool
- type Actor
- type Attribute
- type CRUD
- type Capability
- type Commander
- type Config
- type CursorDirection
- type CursorSpec
- type CursorValues
- type DataType
- type Defaults
- type DefaultsConfig
- type DefaultsManager
- func (dm *DefaultsManager) GetDefaultGetQuery() Query
- func (dm *DefaultsManager) GetDefaultListQuery() Query
- func (dm *DefaultsManager) MergeGetQuery(qarg ...Query) Query
- func (dm *DefaultsManager) MergeListQuery(qarg ...Query) Query
- func (dm *DefaultsManager) SetDefaultGetQuery(q Query)
- func (dm *DefaultsManager) SetDefaultListQuery(q Query)
- type FieldSpec
- type Fields
- type FilterOperatorSpec
- type FilterSpec
- func And(filters ...*FilterSpec) *FilterSpec
- func Between(field string, lo, hi any) *FilterSpec
- func Eq(field string, value any) *FilterSpec
- func Exists(field string, value any) *FilterSpec
- func F(field *FieldSpec, value any) *FilterSpec
- func FILike(field *FieldSpec, value any) *FilterSpec
- func FLike(field *FieldSpec, value any) *FilterSpec
- func Fop(field *FieldSpec, operator FilterOperatorSpec, value any) *FilterSpec
- func Gt(field string, value any) *FilterSpec
- func Gte(field string, value any) *FilterSpec
- func Has(relation string, inner ...*FilterSpec) *FilterSpec
- func ILike(field string, value any) *FilterSpec
- func In(field string, value any) *FilterSpec
- func Like(field string, value any) *FilterSpec
- func Lt(field string, value any) *FilterSpec
- func Lte(field string, value any) *FilterSpec
- func Ne(field string, value any) *FilterSpec
- func NewFilterSpec(field *FieldSpec, operator FilterOperatorSpec, value any) *FilterSpec
- func NewFilterSpecAndGroup(filters ...*FilterSpec) *FilterSpec
- func NewFilterSpecOrGroup(filters ...*FilterSpec) *FilterSpec
- func NotIn(field string, value any) *FilterSpec
- func NotLike(field string, value any) *FilterSpec
- func Or(filters ...*FilterSpec) *FilterSpec
- type Filters
- type JSONColumn
- type NamingConfig
- type Option
- type Options
- type PaginationSpec
- func (p *PaginationSpec) Clone() *PaginationSpec
- func (p *PaginationSpec) GetPageNum() int
- func (p *PaginationSpec) GetPageSize() int
- func (p *PaginationSpec) IsPaginated() bool
- func (p *PaginationSpec) MergeWith(other *PaginationSpec) *PaginationSpec
- func (p *PaginationSpec) String() string
- func (p *PaginationSpec) ToLimitOffset() (int, int)
- type PreloadSpec
- type Preloads
- type Querier
- type Query
- type RelationRef
- type Rewrapper
- type Schema
- func (s Schema) Attributes() []Attribute
- func (s Schema) Filterable(name string) bool
- func (s Schema) IsZero() bool
- func (s Schema) Lookup(name string) (Attribute, bool)
- func (s Schema) Queryable(name string) bool
- func (s Schema) Sortable(name string) bool
- func (s Schema) ValidateQuery(q Query) error
- func (s Schema) With(overrides ...Attribute) Schema
- func (s Schema) Writable(name string, op WriteOp) bool
- type SchemaOption
- type SortDirection
- type SortNullsPosition
- type SortSpec
- type Sorts
- type Transactor
- type TxCRUD
- type Unwrapper
- type WriteOp
Constants ¶
const (
// PageSizeDefault is a GLOBAL per-package default page size if no pagination was specified.
PageSizeDefault = 100
)
Variables ¶
var ( // ErrInvalidCursor is returned when a cursor token cannot be decoded. ErrInvalidCursor = errors.New("invalid cursor token") // ErrCursorRequiresSort is returned when cursor pagination is used without any sort columns. ErrCursorRequiresSort = errors.New("cursor pagination requires at least one sort column") )
Sentinel errors for cursor pagination.
var ( // ErrUnknownField is returned when a referenced field is not declared by the schema. ErrUnknownField = errors.New("unknown field") // ErrFieldNotFilterable is returned when a non-filterable field appears in Query.Filters. ErrFieldNotFilterable = errors.New("field is not filterable") // ErrFieldNotSortable is returned when a non-sortable field appears in Query.Sorts. ErrFieldNotSortable = errors.New("field is not sortable") // ErrFieldNotQueryable is returned when a non-queryable field appears in Query.Fields. ErrFieldNotQueryable = errors.New("field is not queryable") )
Schema validation errors. Schema.ValidateQuery wraps the offending field name (fmt.Errorf("%w: %q", err, name)) so a consumer can surface a useful 400-class message instead of leaking a backend driver error (which would otherwise be a 500).
var ErrInvalidIdentifier = errors.New("invalid identifier")
ErrInvalidIdentifier is returned when a field name contains characters that are not valid SQL identifiers.
var ErrInvalidPatchField = errors.New("invalid patch field")
ErrInvalidPatchField is returned by a write (Patch, or full Update SET-shaping) when a field name does not match any attribute in the schema, or names an attribute that is not mutable (e.g. PK, created_at, soft-delete, immutable).
var ErrNoPatchFields = errors.New("patch requires at least one field")
ErrNoPatchFields is returned by Patch when the Fields list is empty or nil.
var ErrNotFound = errors.New("r3: record not found")
ErrNotFound is returned by Get (and other single-record operations) when no record matches the requested ID.
Every backend normalizes its native "no rows" / "no documents" error to this sentinel — database/sql's sql.ErrNoRows, GORM's gorm.ErrRecordNotFound, MongoDB's mongo.ErrNoDocuments, and the file engine's internal not-found error all surface as r3.ErrNotFound. This lets business code detect a missing record identically regardless of the concrete driver:
user, err := repo.Get(ctx, id)
if errors.Is(err, r3.ErrNotFound) {
// respond 404, etc.
}
var ErrTransactionsNotSupported = errors.New("r3: transactions not supported by this repository")
ErrTransactionsNotSupported is returned by InTx when the repository does not implement the Transactor interface.
var SystemActor = Actor{ID: "", Type: "system"}
SystemActor is the default actor used when no actor is set in the context.
Functions ¶
func As ¶
func As[C any, T any, ID comparable](repo CRUD[T, ID]) (C, bool)
As finds the first layer in the decorator chain (starting at repo and following Unwrapper.Unwrap) that implements C, enabling capability detection through decorators. For example, to reach a backend's soft-delete support regardless of how many decorators wrap it:
sd, ok := r3.As[SoftDeleter[ID]](repo)
It returns the zero value of C and false if no layer implements C.
func EncodeCursor ¶
func EncodeCursor(cv CursorValues) (string, error)
EncodeCursor serializes cursor values into an opaque base64 token.
func ExtractBetweenBounds ¶
ExtractBetweenBounds extracts low and high values from a between filter value. The value must be a slice or array with exactly 2 elements.
func FieldsToStrings ¶
FieldsToStrings converts Fields to a []string of field names. This is a backend-agnostic helper reused by sqlbase, mongobase, etc.
func FinalizeCount ¶
FinalizeCount returns (entities, totalCount) with the correct total. If pagination was not active, totalCount is simply len(entities). This is a backend-agnostic helper reused by sqlbase, mongobase, etc.
func FinalizeCountCursor ¶
FinalizeCountCursor returns (entities, -1) for cursor-paginated results, since total count is not available with keyset pagination.
func InTx ¶
func InTx[T any, ID comparable]( ctx context.Context, repo CRUD[T, ID], fn func(tx CRUD[T, ID]) error, ) error
InTx runs fn inside a transaction if the repository supports it. It begins a transaction, calls fn with a transactional CRUD, and:
- commits if fn returns nil
- rolls back if fn returns an error or panics
The repository may be wrapped in decorators (validation, history, permissions, ...). InTx walks the decorator chain to the backend Transactor, begins the transaction there, and re-applies the same decorator stack on top of the transaction-bound CRUD. This means the CRUD passed to fn is fully decorated, so decorated behaviour still runs for writes inside the transaction rather than being bypassed.
Returns ErrTransactionsNotSupported if no layer in the chain implements Transactor.
Example:
err := r3.InTx(ctx, userRepo, func(tx r3.CRUD[User, int64]) error {
user, err := tx.Create(ctx, newUser)
if err != nil {
return err
}
return nil
})
func SupportsTx ¶
func SupportsTx[T any, ID comparable](repo CRUD[T, ID]) bool
SupportsTx reports whether repo's decorator chain reaches a backend that implements Transactor. Unlike a direct type assertion it sees through decorators, and unlike As it does not treat an intermediate decorator that merely exposes BeginTx as proof of support — only the backend counts.
func ValidateIdentifier ¶
ValidateIdentifier checks that s is a safe SQL identifier or dotted identifier path. Each dot-separated segment must match [a-zA-Z_][a-zA-Z0-9_]*. Examples of valid identifiers: "id", "user_name", "user.profile", "orders.items.product_name". Examples of invalid identifiers: "", "1col", "a b", "x;y", "col--", "table.*".
func WithActor ¶
WithActor returns a new context with the given Actor attached. Typically called in HTTP middleware after authentication:
ctx := r3.WithActor(r.Context(), r3.Actor{ID: "42", Type: "user"})
Authorization data can ride along on Claims, where policies read it back (e.g. via the permissions feature's AccessRequest.Actor):
ctx := r3.WithActor(r.Context(), r3.Actor{ID: "42", Type: "user", Claims: principal})
func WithoutWriteGuard ¶
WithoutWriteGuard returns a context that skips the engine's write-capability enforcement for writes made with it. Use it for audited system/worker writes of a user-immutable column; prefer the SystemWriter wrapper for ergonomics.
It does not bypass the structural floor (computed/PK remain unwritable), and it does not bypass any decorator (history/metrics still record the write).
func WriteGuardBypassed ¶
WriteGuardBypassed reports whether the context carries the write-guard bypass. Engines consult it before enforcing Creatable/Mutable.
Types ¶
type Actor ¶
type Actor struct {
// ID identifies who performed the action (e.g. user ID, API key ID, service name).
ID string
// Type categorizes the actor (e.g. "user", "service", "system", "cron").
Type string
// Claims carries optional application-defined authorization data attached to
// the actor (roles, group/tenant memberships, scopes, capabilities, ...).
// R3 itself never inspects Claims — it only propagates it on the context so
// policies can read it back. The permissions feature passes it through to
// Checker/Scoper via AccessRequest.Actor, letting an authorization policy ride
// entirely on the canonical actor instead of a separate, parallel context key.
//
// It is intentionally untyped (any): the application owns the shape and
// type-asserts it (commonly to a pointer of its own principal type). A nil
// Claims is the norm for system/service actors and the attribution-only
// features (metrics, history), which ignore it.
Claims any
}
Actor represents the identity performing a CRUD operation. It is stored in context.Context and automatically picked up by features like metrics and history for attribution.
A zero-value Actor represents the system/anonymous actor.
type Attribute ¶
type Attribute struct {
// Name is the public/wire name, e.g. "created_at" (snake_case). Filters,
// sorts, and selected fields reference attributes by this name; the engine
// translates it to a physical column/path.
Name string
// Type is the logical data type, used to pick default operators and drive
// frontend filter widgets.
Type DataType
// Caps is the bitset of capabilities (Filterable, Sortable, ...).
Caps Capability
// Ops are the filter operators allowed for this attribute. Derivation fills
// it from the Type's defaults when the attribute is filterable; nil means
// "the defaults for Type" (or none, when not filterable).
Ops []FilterOperatorSpec
// Enum holds the allowed values when Type == TypeEnum.
Enum []string
// Relation describes the target when Type == TypeRel.
Relation *RelationRef
// Computed marks an attribute with no backing column (reserved; computed
// execution is out of scope — see the schema design doc, §8). A computed
// attribute can never be written: the structural floor has nowhere to put a
// value, so no escape hatch can corrupt it.
Computed bool
}
Attribute is one declared, capability-bearing member of an entity.
func (Attribute) Has ¶
func (a Attribute) Has(c Capability) bool
Has reports whether the attribute carries every capability in c.
type CRUD ¶
type CRUD[T any, ID comparable] interface { Querier[T, ID] Commander[T, ID] }
CRUD is the full read+write repository interface. It composes Querier (Get, List) and Commander (Create, Update, Patch, Delete).
func SystemWriter ¶
func SystemWriter[T any, ID comparable](repo CRUD[T, ID]) CRUD[T, ID]
SystemWriter wraps the top of a repository (decorator) chain so its write methods inject WithoutWriteGuard into the context before delegating. The full chain still runs — history/metrics/soft-delete all see the write — but the engine skips the Creatable/Mutable check. Reads and the structural floor are unaffected.
r3.SystemWriter(repo).Update(ctx, feedRow) // writes a user-immutable column, audited
type Capability ¶
type Capability uint8
Capability is a bitset of what an Attribute is allowed to do. The five capabilities are the public contract — the ceiling of what any API caller may do. The permissions feature can only narrow this per-actor/row, never widen it (see the schema design doc, §2.3).
const ( // Filterable means the attribute may appear in Query.Filters. Filterable Capability = 1 << iota // Sortable means the attribute may appear in Query.Sorts. Sortable // Queryable means the attribute may appear in Query.Fields (SELECT) and in serialized output. Queryable // Creatable means the attribute may be set by Create. Creatable // Mutable means the attribute may be changed after creation — gates both Update and Patch. Mutable )
type Commander ¶
type Commander[T any, ID comparable] interface { // Create inserts a new record into the database. Create(context.Context, T) (T, error) // Update modifies an existing record in the database with optional parameters. Update(context.Context, T) (T, error) // Patch performs a partial update, modifying only the columns specified by Fields. // The entity must have its primary key set. Only the fields named in the Fields // list are written to the database; all other columns remain unchanged. // Returns the full entity after the update. Patch(context.Context, T, Fields) (T, error) // Delete removes a record by its ID. // It can use soft delete (if it's turned on the repository level) Delete(context.Context, ID) error }
Commander is the write-only subset of repository operations. It provides methods for creating, modifying, and deleting entities.
type Config ¶
type Config struct {
// Naming controls how R3 maps well-known fields to storage column names.
Naming NamingConfig
// Defaults controls default query behavior (e.g. page size).
Defaults DefaultsConfig
}
Config holds framework-level configuration for R3 repositories. It controls naming conventions, default query behavior, and other cross-cutting concerns that are not specific to any single model.
Use DefaultConfig to get a Config with sensible defaults, then override individual fields as needed.
Config is intended to be read-only after construction. Pass it to engine/driver constructors via WithConfig.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with all defaults applied.
type CursorDirection ¶
type CursorDirection int8
CursorDirection indicates whether cursor pagination goes forward or backward.
const ( // CursorForward pages forward from the "after" cursor position. CursorForward CursorDirection = iota // CursorBackward pages backward from the "before" cursor position. CursorBackward )
func (CursorDirection) String ¶
func (d CursorDirection) String() string
String returns "forward" or "backward".
type CursorSpec ¶
type CursorSpec struct {
// After is the opaque cursor token for forward pagination.
After string
// Before is the opaque cursor token for backward pagination.
Before string
// Limit is the maximum number of results to return. 0 means use default.
Limit int
}
CursorSpec specifies cursor/keyset-based pagination. At most one of After or Before should be set. If both are set, After takes precedence. Limit controls the maximum number of items returned per page.
func NewCursorAfter ¶
func NewCursorAfter(after string, limit int) *CursorSpec
NewCursorAfter creates a CursorSpec for forward pagination after the given token.
func NewCursorBefore ¶
func NewCursorBefore(before string, limit int) *CursorSpec
NewCursorBefore creates a CursorSpec for backward pagination before the given token.
func NewCursorFirst ¶
func NewCursorFirst(limit int) *CursorSpec
NewCursorFirst creates a CursorSpec for the first page (no cursor, just limit).
func (*CursorSpec) Clone ¶
func (c *CursorSpec) Clone() *CursorSpec
Clone creates a deep copy of the CursorSpec.
func (*CursorSpec) Direction ¶
func (c *CursorSpec) Direction() CursorDirection
Direction returns CursorForward or CursorBackward based on which token is set.
func (*CursorSpec) GetLimit ¶
func (c *CursorSpec) GetLimit() int
GetLimit returns the limit, defaulting to PageSizeDefault if not set.
func (*CursorSpec) MergeWith ¶
func (c *CursorSpec) MergeWith(other *CursorSpec) *CursorSpec
MergeWith merges this cursor spec with another, with other taking precedence.
func (*CursorSpec) String ¶
func (c *CursorSpec) String() string
String returns a debug-friendly string representation.
func (*CursorSpec) Token ¶
func (c *CursorSpec) Token() string
Token returns the active cursor token (After takes precedence over Before).
type CursorValues ¶
CursorValues holds the sort-column values that define the cursor position. Keys are column names; values are the column values at that position.
func DecodeCursor ¶
func DecodeCursor(token string) (CursorValues, error)
DecodeCursor deserializes an opaque base64 token back into CursorValues. Returns empty CursorValues (not nil) for an empty token.
type DataType ¶
type DataType string
DataType is the logical type of an attribute. It is engine-agnostic — it drives the default filter operators and (later) a frontend's filter widgets, not any storage representation.
type Defaults ¶
Defaults stores the default query values for List and Get operations. It is shared by all drivers (database/sql-based, GORM, Bun, go-pg, MongoDB, etc.).
func NewDefaults ¶
func NewDefaults() Defaults
NewDefaults returns Defaults initialized with reasonable default queries.
type DefaultsConfig ¶
type DefaultsConfig struct {
// PageSize is the default number of items per page when pagination
// is active but no explicit page size is provided.
// Default: 100 (same as PageSizeDefault)
PageSize int
// Unpaginated, when true, makes List return ALL matching rows by default —
// no implicit page-size cap. Individual queries can still opt back into
// pagination per call by setting Query.Pagination. Takes precedence over
// PageSize.
//
// Use with care on large tables; prefer the per-query r3.Unpaginated()
// escape hatch when only some call sites need everything.
Unpaginated bool
}
DefaultsConfig controls default query behavior.
type DefaultsManager ¶
type DefaultsManager struct {
// contains filtered or unexported fields
}
DefaultsManager provides thread-safe access to Defaults. Embed this in your CRUD struct to get SetDefaultListQuery, SetDefaultGetQuery, GetDefaultListQuery, and GetDefaultGetQuery for free.
func NewDefaultsManager ¶
func NewDefaultsManager() DefaultsManager
NewDefaultsManager creates a DefaultsManager with reasonable defaults.
func NewDefaultsManagerWithConfig ¶
func NewDefaultsManagerWithConfig(cfg Config) DefaultsManager
NewDefaultsManagerWithConfig creates a DefaultsManager that respects the given Config.
If Config.Defaults.Unpaginated is set, the default list query is unbounded (List returns all rows unless a query opts into pagination). Otherwise, if Config.Defaults.PageSize differs from the global default, the default list query is initialized with that page size.
func (*DefaultsManager) GetDefaultGetQuery ¶
func (dm *DefaultsManager) GetDefaultGetQuery() Query
GetDefaultGetQuery returns the default GetQuery (thread-safe).
func (*DefaultsManager) GetDefaultListQuery ¶
func (dm *DefaultsManager) GetDefaultListQuery() Query
GetDefaultListQuery returns the default ListQuery (thread-safe).
func (*DefaultsManager) MergeGetQuery ¶
func (dm *DefaultsManager) MergeGetQuery(qarg ...Query) Query
MergeGetQuery merges the given query args with the default get query.
func (*DefaultsManager) MergeListQuery ¶
func (dm *DefaultsManager) MergeListQuery(qarg ...Query) Query
MergeListQuery merges the given query args with the default list query.
func (*DefaultsManager) SetDefaultGetQuery ¶
func (dm *DefaultsManager) SetDefaultGetQuery(q Query)
SetDefaultGetQuery sets the default GetQuery (thread-safe).
func (*DefaultsManager) SetDefaultListQuery ¶
func (dm *DefaultsManager) SetDefaultListQuery(q Query)
SetDefaultListQuery sets the default ListQuery (thread-safe).
type FieldSpec ¶
type FieldSpec string
FieldSpec is the simplest possible implementation of a field. FieldSpec is just a string - it can be the name of the field in database, etc.
func NewFieldSpec ¶
type Fields ¶
type Fields []*FieldSpec
Fields is a slice of *FieldSpec.
type FilterOperatorSpec ¶
type FilterOperatorSpec int8
FilterOperatorSpec represents an operator to be used in a filter. For now, not all r3 operators are supported by every possible dialect. But the idea is to provide here in r3 the most possibly full list of operators that we need. TODO: might be refactored into a more complex struct
const ( OperatorUnspecified FilterOperatorSpec = iota OperatorEq // = OperatorNe // != OperatorExists // exists OperatorGt // > OperatorGte // >= OperatorLt // < OperatorLte // <= OperatorBetween // between_inc meaning [] OperatorBetweenEx // between_exc meaning () OperatorBetweenExInc // between_exc_inc meaning (] OperatorBetweenIncEx // between_inc_exc meaning [) OperatorIn // in OperatorNotIn // not in OperatorLike // like OperatorNotLike // not like OperatorILike // ilike (like + case insensitive) )
func (*FilterOperatorSpec) String ¶
func (op *FilterOperatorSpec) String() string
String is implemented for debugging purposes, so the FilterOperatorSpec is a fmt.Stringer. Note: Protect with the `exhausted` linter.
type FilterSpec ¶
type FilterSpec struct {
Field *FieldSpec
Operator FilterOperatorSpec
Value any
// AND Children should be declared inside AND
And Filters
// OR Children should be declared inside OR
Or Filters
// Relation, when non-empty, makes this a relationship ("has") filter: it
// matches rows whose declared relation `Relation` (by the same struct field
// name used for preloads) has at least one related row satisfying all of
// RelationFilter. Field/Operator/Value/And/Or are ignored when Relation is
// set.
//
// Relationship filters are resolved by the driver into a key-set In filter
// before SQL translation (see the GORM driver's relation lowering), so they
// work on any backend regardless of native subquery support. A dialect may
// later compile them natively (EXISTS) as an optimization.
// omitempty so a non-relationship filter serializes exactly as before (the
// relationship fields are absent unless used).
Relation string `json:",omitempty"`
RelationFilter Filters `json:",omitempty"`
}
FilterSpec represents a filtering criteria with a field, an operator, and a value.
func And ¶
func And(filters ...*FilterSpec) *FilterSpec
And is a shortcut for NewFilterSpecAndGroup.
func Between ¶
func Between(field string, lo, hi any) *FilterSpec
Between builds an inclusive `field BETWEEN lo AND hi` filter.
func Exists ¶
func Exists(field string, value any) *FilterSpec
Exists builds a `field exists` filter (presence check). The value is the expected existence as a bool where the backend supports it.
func F ¶
func F(field *FieldSpec, value any) *FilterSpec
F is a shorthand for NewFilterSpec(field, OperatorEq, value).
func FILike ¶
func FILike(field *FieldSpec, value any) *FilterSpec
func FLike ¶
func FLike(field *FieldSpec, value any) *FilterSpec
func Fop ¶
func Fop(field *FieldSpec, operator FilterOperatorSpec, value any) *FilterSpec
Fop is a shorthand for NewFilterSpec() Fop is "F" for filter and "op" for operator.
func Has ¶
func Has(relation string, inner ...*FilterSpec) *FilterSpec
Has builds a relationship ("has") filter: it matches rows whose declared relation `relation` (by struct field name — the same name used for preloads) has at least one related row satisfying all of `inner`. Example:
r3.Has("Squads", r3.In("id", []int64{1, 3})) // rows linked to squad 1 or 3
The inner filters are evaluated against the related entity. Drivers resolve the relation to a key set and rewrite this to an In filter, so it works on every backend regardless of native subquery support.
Resolution happens in the driver, so a Has filter does not round-trip through the serialization dialects (json/url/yaml/toml) — build it in Go. When used as a permission scope, enforcing it on Get requires permissions.WithIDFunc.
func ILike ¶
func ILike(field string, value any) *FilterSpec
ILike builds a case-insensitive `field ILIKE value` filter.
func In ¶
func In(field string, value any) *FilterSpec
In builds a `field IN (values...)` filter. The value is typically a slice.
func Like ¶
func Like(field string, value any) *FilterSpec
Like builds a case-sensitive `field LIKE value` filter.
func NewFilterSpec ¶
func NewFilterSpec(field *FieldSpec, operator FilterOperatorSpec, value any) *FilterSpec
NewFilterSpec constructs a FilterSpec (not an AND/OR group).
func NewFilterSpecAndGroup ¶
func NewFilterSpecAndGroup(filters ...*FilterSpec) *FilterSpec
func NewFilterSpecOrGroup ¶
func NewFilterSpecOrGroup(filters ...*FilterSpec) *FilterSpec
func NotIn ¶
func NotIn(field string, value any) *FilterSpec
NotIn builds a `field NOT IN (values...)` filter. The value is typically a slice.
func NotLike ¶
func NotLike(field string, value any) *FilterSpec
NotLike builds a case-sensitive `field NOT LIKE value` filter.
func (*FilterSpec) Clone ¶
func (f *FilterSpec) Clone() *FilterSpec
Clone returns a deep clone of the filter.
func (*FilterSpec) String ¶
func (f *FilterSpec) String() string
String returns just a string representation of the filter (as JSON). As all fields are exported, we're OK with this.
type Filters ¶
type Filters []*FilterSpec
Filters represents a list of *FilterSpec.
type JSONColumn ¶
type JSONColumn[T any] struct { Val T }
JSONColumn is a generic wrapper that stores a value of type T as a JSON string in SQL databases. It implements sql.Scanner, driver.Valuer, json.Marshaler, and json.Unmarshaler so it works transparently with both SQL drivers and JSON APIs.
Use it for struct fields that should be persisted as a JSON blob in a single column (e.g. []FieldChange, Metadata maps, nested config objects).
Zero dependencies beyond database/sql/driver and encoding/json (both stdlib).
func NewJSONColumn ¶
func NewJSONColumn[T any](v T) JSONColumn[T]
NewJSONColumn creates a JSONColumn wrapping the given value.
func (JSONColumn[T]) MarshalJSON ¶
func (j JSONColumn[T]) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler — transparent JSON serialization of the wrapped value.
func (*JSONColumn[T]) Scan ¶
func (j *JSONColumn[T]) Scan(src any) error
Scan implements sql.Scanner — reads a JSON string (or []byte) from SQL and unmarshals into T.
func (*JSONColumn[T]) UnmarshalJSON ¶
func (j *JSONColumn[T]) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler — transparent JSON deserialization into the wrapped value.
type NamingConfig ¶
type NamingConfig struct {
// CreatedAtField is the storage column name for creation timestamps.
// Default: "created_at"
CreatedAtField string
// UpdatedAtField is the storage column name for update timestamps.
// Default: "updated_at"
UpdatedAtField string
// DeletedAtField is the storage column name for soft-delete timestamps.
// Default: "deleted_at"
DeletedAtField string
}
NamingConfig controls how R3 maps well-known fields to storage column names. Empty strings mean "use the default".
type Option ¶
type Option func(*Options)
Option is a functional option for configuring R3 repositories. Pass options to engine/driver constructors to customize behavior.
func WithConfig ¶
WithConfig sets the R3 framework-level configuration.
type Options ¶
type Options struct {
// Config is the framework-level configuration.
Config Config
}
Options holds the resolved configuration for a repository. Engine and driver constructors call ResolveOptions to apply functional options and get the final values.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns Options initialized with sensible defaults.
func ResolveOptions ¶
ResolveOptions applies functional options to the default Options and returns the result.
type PaginationSpec ¶
type PaginationSpec struct {
// PageNum is 1-indexed page number (1 = first page).
PageNum maybe.Int
// PageSize is the number of items per page.
PageSize maybe.Int
}
PaginationSpec is the core pagination type using PageNum/PageSize.
func DefaultPagination ¶
func DefaultPagination() *PaginationSpec
DefaultPagination returns a PaginationSpec with default page size.
func NewPaginationSpec ¶
func NewPaginationSpec(pageNum int, pageSize ...int) *PaginationSpec
NewPaginationSpec creates a new PaginationSpec with given page number and page size.
func NewPaginationSpecWithSize ¶
func NewPaginationSpecWithSize(pageSize int) *PaginationSpec
NewPaginationSpecWithSize creates a new PaginationSpec with only page size specified.
func NoPagination ¶
func NoPagination() *PaginationSpec
NoPagination returns a PaginationSpec with no pagination limits.
func Unpaginated ¶
func Unpaginated() *PaginationSpec
Unpaginated returns a PaginationSpec that disables the default page-size cap, so List returns every matching record.
By default List paginates (PageSizeDefault items per page); a caller that expects "give me everything" would otherwise silently get a truncated slice. Make the intent explicit:
all, total, err := repo.List(ctx, r3.Query{Pagination: r3.Unpaginated()})
Passing this on a query overrides any configured default page size (it clears the inherited cap during query merge). To make a whole repo unpaginated by default instead, set Config.Defaults.Unpaginated.
Unpaginated is an intention-revealing alias for NoPagination.
func (*PaginationSpec) Clone ¶
func (p *PaginationSpec) Clone() *PaginationSpec
Clone creates a deep copy of the PaginationSpec.
func (*PaginationSpec) GetPageNum ¶
func (p *PaginationSpec) GetPageNum() int
GetPageNum returns the page number (1-indexed), defaulting to 1 if not set.
func (*PaginationSpec) GetPageSize ¶
func (p *PaginationSpec) GetPageSize() int
GetPageSize returns the page size, defaulting to PageSizeDefault if not set.
func (*PaginationSpec) IsPaginated ¶
func (p *PaginationSpec) IsPaginated() bool
IsPaginated returns true if pagination is configured.
func (*PaginationSpec) MergeWith ¶
func (p *PaginationSpec) MergeWith(other *PaginationSpec) *PaginationSpec
MergeWith merges this pagination with another, with other taking precedence.
func (*PaginationSpec) String ¶
func (p *PaginationSpec) String() string
String returns string representation of pagination.
func (*PaginationSpec) ToLimitOffset ¶
func (p *PaginationSpec) ToLimitOffset() (int, int)
ToLimitOffset converts PageNum/PageSize to Limit/Offset for database queries.
type PreloadSpec ¶
type PreloadSpec struct {
Name string
}
PreloadSpec means a simple possible preload (name of a table/collection).
func NewPreloadSpec ¶
func NewPreloadSpec(name string) *PreloadSpec
NewPreloadSpec creates a PreloadSpec.
func (*PreloadSpec) Clone ¶
func (t *PreloadSpec) Clone() *PreloadSpec
Clone returns a newly created struct of PreloadSpec.
func (*PreloadSpec) GetName ¶
func (t *PreloadSpec) GetName() string
GetName returns the name of the current preload.
func (*PreloadSpec) String ¶
func (t *PreloadSpec) String() string
String simply returns name of the preload.
type Preloads ¶
type Preloads []*PreloadSpec
Preloads is a slice of *PreloadSpec.
type Querier ¶
type Querier[T any, ID comparable] interface { // Get retrieves a record by its ID with optional parameters. Get(context.Context, ID, ...Query) (T, error) // List retrieves records based on the provided query parameters. List(context.Context, ...Query) ([]T, int64, error) // Count returns the number of records matching the query's filters. // // Only Filters and IncludeTrashed affect the result — pagination, sorts, // fields, and preloads are ignored. Called with no query it counts every // (non-trashed) record. It is the efficient way to answer "how many?" // without materializing rows. Count(context.Context, ...Query) (int64, error) }
Querier is the read-only subset of repository operations. It provides methods for retrieving entities without modifying them.
Use Querier when you need read-only access to a repository, for example when reading configuration or building reports.
type Query ¶
type Query struct {
Pagination *PaginationSpec
// Cursor enables keyset/cursor-based pagination as an alternative to offset-based.
// When set, Cursor takes precedence over Pagination. The two are mutually exclusive.
Cursor *CursorSpec
Fields Fields // Specific fields to retrieve.
Filters Filters // []*FilterSpec
Sorts Sorts // []*SortSpec
Preloads Preloads // []*PreloadSpec
// IncludeTrashed when true will still return trashed (soft-deleted) records.
IncludeTrashed maybe.Bool
}
Query wraps everything from r3: Pagination, Fields, Filters, Sorts, Preloads.
func DefaultQuery ¶
func DefaultQuery() Query
DefaultQuery returns the default Query (with reasonable params).
func (Query) MergeWith ¶
MergeWith merges this query with another, returning a new Query (no mutation).
Fields, Filters, and Preloads accumulate (the union of both). Sorts and Pagination OVERRIDE: when other specifies them, they replace the inherited values rather than stacking under them. This makes other the higher-precedence layer — typically a per-call query merged over a repo's defaults.
type RelationRef ¶
type RelationRef struct {
// Target is the related entity's logical name (snake_case of its type).
Target string
// Kind is the relationship kind ("has-many", "belongs-to", "many-to-many").
Kind string
// Label is the attribute on the target used as a human-facing label
// (optional; populated via With for introspection).
Label string
}
RelationRef points an Attribute (Type == TypeRel) at its target entity.
type Rewrapper ¶
type Rewrapper[T any, ID comparable] interface { Rewrap(inner CRUD[T, ID]) CRUD[T, ID] }
Rewrapper is implemented by decorators that can rebuild themselves around a different inner CRUD. It lets InTx/BeginTxChain re-apply the decorator stack on top of a transaction-bound CRUD, so decorated behaviour (validation, history, permissions, ...) still runs inside the transaction instead of being bypassed.
Rewrap must return a decorator equivalent to the receiver but delegating to inner. Stateful decorators should share their backing state (stores, locks) with the rebuilt instance.
type Schema ¶
type Schema struct {
// contains filtered or unexported fields
}
Schema is the logical, engine-agnostic descriptor of an entity: an ordered set of capability-bearing Attributes plus lookup helpers. It lives in the core r3 package next to Query/Filters and contains no storage details (no SQL column names, no BSON paths) — each engine owns the physical binding, keyed by attribute name.
A Schema is immutable: derive one with SchemaOf and layer overrides with With, both of which return a fresh Schema. The zero Schema has no attributes; ValidateQuery treats it as "no schema" and validates nothing, which keeps the engine seam back-compatible for callers that pass no schema.
Vocabulary note: an Attribute is a declared member of an entity, whereas a Field/FieldSpec is a reference to one inside a Query. The Schema is the set of valid Fields, plus what each is allowed to do.
func SchemaOf ¶
func SchemaOf[T any](opts ...SchemaOption) Schema
SchemaOf reflects T's struct tags into a logical Schema with default capabilities (see the schema design doc, §2.7): a plain scalar column is queryable, filterable, sortable, creatable, and mutable; the PK, the created_at/updated_at timestamps, and the soft-delete column are read-only; relations are queryable (preload) only. Tags only ever tighten these defaults.
The default (no-option) result is cached per type T.
func (Schema) Attributes ¶
Attributes returns the schema's attributes in declaration order. The returned slice is a copy; mutating it does not affect the Schema.
func (Schema) Filterable ¶
Filterable reports whether the named attribute may appear in Query.Filters.
func (Schema) IsZero ¶
IsZero reports whether the schema declares no attributes. A zero Schema disables validation at the engine seam (back-compat for schema-less callers).
func (Schema) ValidateQuery ¶
ValidateQuery is the source of typed, 400-class errors. It checks every field referenced in Filters, Sorts, and Fields against the schema's capabilities and returns a typed error (wrapping the offending field name) on the first violation. A zero Schema validates nothing (back-compat for schema-less callers); see Schema.IsZero.
Dotted ("relation.path") field names are skipped here: they reference another entity and are validated by the engine against the target schema (TODO), not the root schema. Relationship ("has") filters are likewise skipped.
func (Schema) With ¶
With returns a new Schema with the given attributes layered on top: an override whose Name matches an existing attribute replaces it in place; a new Name is appended. The receiver is left unchanged.
Use it for what tags cannot express — computed attributes, relation labels, tightened operator sets — keeping the Schema immutable.
type SchemaOption ¶
type SchemaOption func(*schemaConfig)
SchemaOption customizes schema derivation.
func WithSchemaNaming ¶
func WithSchemaNaming(n NamingConfig) SchemaOption
WithSchemaNaming overrides the well-known field names (created_at, updated_at, deleted_at) used to derive the read-only timestamp/soft-delete defaults. By default the standard names apply.
type SortDirection ¶
type SortDirection int8
SortDirection represents a direction of sorting.
const ( // SortDirectionUnspecified means that direction is not specified by user and will be fallback to default. SortDirectionUnspecified SortDirection = iota // SortDirectionAsc means that sorting will be done in ascending order. SortDirectionAsc // SortDirectionDesc means that sorting will be done in descending order. SortDirectionDesc )
func (SortDirection) String ¶
func (s SortDirection) String() string
String is implemented for debugging purposes, so the SortDirection is a fmt.Stringer.
type SortNullsPosition ¶
type SortNullsPosition int8
SortNullsPosition represents a position of nulls in sort (first or last).
const ( // NullsPositionNotSpecified means that nulls position won't be specified in the query. NullsPositionNotSpecified SortNullsPosition = iota // NullsPositionFirst means that nulls will be placed first in the result. NullsPositionFirst // NullsPositionLast means that nulls will be placed last in the result. NullsPositionLast )
func (SortNullsPosition) String ¶
func (s SortNullsPosition) String() string
String is implemented for debugging purposes, so the SortNullsPosition is a fmt.Stringer.
type SortSpec ¶
type SortSpec struct {
Column *FieldSpec
Direction SortDirection // Asc | Desc | Unspecified (default)
NullsPosition SortNullsPosition // First | Last | Unspecified
}
SortSpec represents a single sort criteria.
func NewSortAscSpec ¶
NewSortAscSpec returns sort by a given field ordered ASC.
func NewSortDescSpec ¶
NewSortDescSpec returns sort by a given field ordered DESC.
func NewSortSpec ¶
func NewSortSpec(col *FieldSpec, direction SortDirection, nullsPositionArg ...SortNullsPosition) *SortSpec
NewSortSpec is the main SortSpec constructor.
func (*SortSpec) GetCriteria ¶
GetCriteria returns the sort criteria.
func (*SortSpec) GetDirection ¶
func (s *SortSpec) GetDirection() SortDirection
GetDirection returns the sort direction.
type Transactor ¶
type Transactor[T any, ID comparable] interface { // BeginTx starts a transaction and returns a [TxCRUD] scoped to that transaction. // All operations on the returned TxCRUD execute within the transaction. // The caller MUST call either Commit or Rollback on the returned TxCRUD. BeginTx(ctx context.Context) (TxCRUD[T, ID], error) }
Transactor is an optional interface that CRUD implementations may satisfy to indicate transaction support. Not every backend supports transactions (e.g. in-memory stores, YAML files, some NoSQL), so this is opt-in.
Use a type assertion to check whether a repository supports transactions:
txr, ok := repo.(r3.Transactor[MyEntity, int64])
if !ok {
// transactions not supported
}
Or use the InTx helper for ergonomic usage with automatic commit/rollback.
type TxCRUD ¶
type TxCRUD[T any, ID comparable] interface { CRUD[T, ID] // Commit commits the transaction. // After Commit, the TxCRUD must not be used. Commit() error // Rollback aborts the transaction. // Rollback is a no-op if Commit was already called. // After Rollback, the TxCRUD must not be used. Rollback() error }
TxCRUD is a transactional CRUD that operates within a single transaction. It embeds CRUD for all standard operations and adds Commit/Rollback to control the transaction lifecycle.
After Commit or Rollback is called, the TxCRUD should not be used for further operations.
func BeginTxChain ¶
func BeginTxChain[T any, ID comparable](ctx context.Context, repo CRUD[T, ID]) (TxCRUD[T, ID], error)
BeginTxChain begins a transaction on the backend Transactor reached by walking repo's decorator chain, and returns a TxCRUD whose CRUD methods are the same decorator stack re-applied on top of the transaction. Commit and Rollback drive the underlying backend transaction.
It is the chain-aware counterpart to calling BeginTx directly on a backend, and is what decorators delegate to so transactions compose with decoration. Returns ErrTransactionsNotSupported if no layer implements Transactor.
type Unwrapper ¶
type Unwrapper[T any, ID comparable] interface { Unwrap() CRUD[T, ID] }
Unwrapper is implemented by decorators that wrap an inner CRUD. Exposing the wrapped CRUD lets capability detection (As) and transaction propagation (InTx, BeginTxChain) walk the decorator chain down to the backend.
Every r3 feature decorator implements Unwrapper.
Source Files
¶
- crud.go
- doc.go
- helpers.go
- r3_actor.go
- r3_config.go
- r3_cursor.go
- r3_defaults.go
- r3_errors.go
- r3_field.go
- r3_filter.go
- r3_filter_operator.go
- r3_json_column.go
- r3_option.go
- r3_pagination.go
- r3_preload.go
- r3_query.go
- r3_schema_bypass.go
- r3_sort.go
- r3_tx.go
- r3_unwrap.go
- schema.go
- schema_caps.go
- schema_derive.go
- schema_validate.go
Directories
¶
| Path | Synopsis |
|---|---|
|
dialects
|
|
|
bson
Package r3bson provides a BSON dialect for converting r3 types to MongoDB BSON documents.
|
Package r3bson provides a BSON dialect for converting r3 types to MongoDB BSON documents. |
|
canonical
Package canonical provides shared parse and format functions for the canonical string representations of r3 query components.
|
Package canonical provides shared parse and format functions for the canonical string representations of r3 query components. |
|
json
Package r3json provides bidirectional conversion between r3 query types and JSON.
|
Package r3json provides bidirectional conversion between r3 query types and JSON. |
|
schema
Package r3schema serializes an r3.Schema to a stable, versioned, public-only JSON shape for introspection.
|
Package r3schema serializes an r3.Schema to a stable, versioned, public-only JSON shape for introspection. |
|
sql
Package r3sql translates r3 query types into SQL clauses.
|
Package r3sql translates r3 query types into SQL clauses. |
|
toml
Package r3toml provides bidirectional conversion between r3 query types and TOML.
|
Package r3toml provides bidirectional conversion between r3 query types and TOML. |
|
url
Package r3url provides bidirectional conversion between r3 query types and URL query parameters.
|
Package r3url provides bidirectional conversion between r3 query types and URL query parameters. |
|
yaml
Package r3yaml provides bidirectional conversion between r3 query types and YAML.
|
Package r3yaml provides bidirectional conversion between r3 query types and YAML. |
|
drivers
|
|
|
bun
Package r3bun provides an r3.CRUD[T, ID] driver backed by Bun, a SQL-first Go ORM for PostgreSQL, MySQL, MSSQL, and SQLite.
|
Package r3bun provides an r3.CRUD[T, ID] driver backed by Bun, a SQL-first Go ORM for PostgreSQL, MySQL, MSSQL, and SQLite. |
|
gopg
Package r3gopg provides an r3.CRUD[T, ID] driver backed by go-pg v10, a PostgreSQL ORM with a focus on PostgreSQL-specific features.
|
Package r3gopg provides an r3.CRUD[T, ID] driver backed by go-pg v10, a PostgreSQL ORM with a focus on PostgreSQL-specific features. |
|
gorm
Package r3gorm provides an r3.CRUD[T, ID] driver backed by GORM.
|
Package r3gorm provides an r3.CRUD[T, ID] driver backed by GORM. |
|
mongo
Package r3mongo provides an r3.CRUD[T, ID] driver backed by the official MongoDB Go driver v2.
|
Package r3mongo provides an r3.CRUD[T, ID] driver backed by the official MongoDB Go driver v2. |
|
mysql
Package r3mysql provides an r3.CRUD[T, ID] driver backed by go-sql-driver/mysql, the pure Go MySQL driver for database/sql.
|
Package r3mysql provides an r3.CRUD[T, ID] driver backed by go-sql-driver/mysql, the pure Go MySQL driver for database/sql. |
|
pgx
Package r3pgx provides an r3.CRUD[T, ID] driver backed by jackc/pgx, the pure Go PostgreSQL driver.
|
Package r3pgx provides an r3.CRUD[T, ID] driver backed by jackc/pgx, the pure Go PostgreSQL driver. |
|
pq
Package r3pq provides an r3.CRUD[T, ID] driver backed by lib/pq, the pure Go PostgreSQL driver for database/sql.
|
Package r3pq provides an r3.CRUD[T, ID] driver backed by lib/pq, the pure Go PostgreSQL driver for database/sql. |
|
sqlite3
Package r3sqlite3 provides an r3.CRUD[T, ID] driver backed by mattn/go-sqlite3, the CGo SQLite3 driver for database/sql.
|
Package r3sqlite3 provides an r3.CRUD[T, ID] driver backed by mattn/go-sqlite3, the CGo SQLite3 driver for database/sql. |
|
engine
|
|
|
file
Package enginefile provides a file-based CRUD engine for r3.
|
Package enginefile provides a file-based CRUD engine for r3. |
|
mongo
Package enginemongo provides the MongoDB engine for r3.
|
Package enginemongo provides the MongoDB engine for r3. |
|
sql
Package enginesql provides the SQL engine for r3.
|
Package enginesql provides the SQL engine for r3. |
|
examples
|
|
|
02petstore
Package petstore demonstrates a full CRUD JSON API server using r3 with GORM and PostgreSQL.
|
Package petstore demonstrates a full CRUD JSON API server using r3 with GORM and PostgreSQL. |
|
02petstore/cmd
command
Pet Store example server.
|
Pet Store example server. |
|
features
|
|
|
history
Package history provides activity log / change tracking for r3 CRUD repositories.
|
Package history provides activity log / change tracking for r3 CRUD repositories. |
|
metrics
Package metrics provides domain-level analytics for r3 CRUD repositories.
|
Package metrics provides domain-level analytics for r3 CRUD repositories. |
|
permissions
Package permissions provides a policy-based, entity-aware permission decorator for r3 CRUD repositories.
|
Package permissions provides a policy-based, entity-aware permission decorator for r3 CRUD repositories. |
|
softdelete
Package softdelete provides a decorator for r3.CRUD[T, ID] that adds Restore and HardDelete capabilities to any CRUD implementation that supports soft-delete.
|
Package softdelete provides a decorator for r3.CRUD[T, ID] that adds Restore and HardDelete capabilities to any CRUD implementation that supports soft-delete. |
|
transactor
Package transactor provides a decorator for r3.CRUD[T, ID] that surfaces transaction capabilities from the underlying driver.
|
Package transactor provides a decorator for r3.CRUD[T, ID] that surfaces transaction capabilities from the underlying driver. |
|
validation
Package validation provides a decorator that validates entities before mutation operations (Create, Update, Patch) on any r3.CRUD[T, ID] repository.
|
Package validation provides a decorator that validates entities before mutation operations (Create, Update, Patch) on any r3.CRUD[T, ID] repository. |
|
internal
|
|
|
tag
Package r3tag provides struct tag parsing for r3 entities.
|
Package r3tag provides struct tag parsing for r3 entities. |
|
utils
Package r3utils provides shared utility functions used across r3 packages.
|
Package r3utils provides shared utility functions used across r3 packages. |