entity

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatValidationErrors

func FormatValidationErrors(errors map[string]string) []string

FormatValidationErrors formats a map of field errors into a user-friendly string slice.

func SeedDataFromContext

func SeedDataFromContext(ctx context.Context) ([]byte, error)

SeedDataFromContext returns the bytes referenced by the entity's SeedFS + SeedPath. Use inside a Seed function:

Seed: func(ctx context.Context, db *sql.DB) error {
    data, err := entity.SeedDataFromContext(ctx)
    if err != nil {
        return err
    }
    var rows []FoodRow
    if err := json.Unmarshal(data, &rows); err != nil {
        return err
    }
    // ...insert rows...
}

Returns an error when no SeedFS was configured on the EntityConfig. Name matches the framework convention (TxFromContext, SessionFromContext, RegistryFromContext); the older *FromCtx shape is a battery/auth outlier.

func WithSeedDataContext

func WithSeedDataContext(ctx context.Context, sfs fs.FS, path string) context.Context

WithSeedDataContext attaches a SeedFS + SeedPath pair to ctx for retrieval by SeedDataFromContext inside a Seed function. The framework calls this internally; hosts should not need to invoke it directly.

Types

type AccessControl

type AccessControl struct {
	Read   string // List + Get
	Create string
	Update string
	Delete string
}

AccessControl declares the RBAC permission required for each CRUD operation on an entity. Each field holds a permission string (e.g. "posts:write"); blank means that operation is not RBAC-gated. Read covers both List and Get.

Permissions are plain strings here so the entity package stays decoupled from framework/access; the CRUD layer converts them to access.Permission and enforces them via access.Can against the policy + roles in the request context.

type BoolColumn

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

BoolColumn represents a BOOLEAN column.

func NewBoolColumn

func NewBoolColumn(name string) BoolColumn

func (BoolColumn) Asc

func (c BoolColumn) Asc() Order

func (BoolColumn) Desc

func (c BoolColumn) Desc() Order

func (BoolColumn) Eq

func (c BoolColumn) Eq(v bool) Condition

func (BoolColumn) IsFalse

func (c BoolColumn) IsFalse() Condition

func (BoolColumn) IsNotNull

func (c BoolColumn) IsNotNull() Condition

func (BoolColumn) IsNull

func (c BoolColumn) IsNull() Condition

func (BoolColumn) IsTrue

func (c BoolColumn) IsTrue() Condition

type Condition

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

Condition is a where-clause fragment plus its bound arguments.

func And

func And(conds ...Condition) Condition

And combines conditions with AND. Useful inside Or(...) to nest a group of ANDed predicates: Or(And(a, b), And(c, d)).

func Not

func Not(c Condition) Condition

Not wraps a condition in NOT (...).

func Or

func Or(conds ...Condition) Condition

Or combines conditions with OR. Each conjunct keeps its own internal argument order; placeholders are renumbered at QueryBuilder.Build time so "$1" in a fragment doesn't collide with another fragment's "$1".

func (Condition) Apply

func (c Condition) Apply(qb *query.QueryBuilder)

Apply appends this condition to the query builder.

func (Condition) Args

func (c Condition) Args() []any

Args returns the bound arguments for the fragment. Pair with SQL().

func (Condition) SQL

func (c Condition) SQL() string

SQL returns the where-clause fragment. Exported so callers outside the entity package (typed_query, etc.) can compose conditions onto query builders directly.

type Endpoint

type Endpoint struct {
	Method      string          `json:"method"`
	Path        string          `json:"path"`
	Name        string          `json:"name,omitempty"`
	Description string          `json:"description,omitempty"`
	MCP         bool            `json:"mcp,omitempty"`
	Handler     http.Handler    `json:"-"`
	MCPHandler  mcp.ToolHandler `json:"-"`
}

Endpoint declares a custom route owned by an entity.

Path may be absolute ("/posts/{id}/publish") or relative to the entity table path ("{id}/publish"). Both Go 1.22 "{id}" and older ":id" parameter syntax are accepted. Handler is used for HTTP. MCPHandler is optional and is only registered when MCP is true.

type Entity

type Entity struct {
	Config     EntityConfig
	DB         *sql.DB
	PrimaryKey string // defaults to "id"
}

Entity represents a registered domain entity with its config and DB handle.

func Define

func Define(name string, config EntityConfig) *Entity

Define creates a new Entity with the given name and configuration. It applies defaults (Table, Timestamps=true) and stores the name. It also injects system fields (id, timestamps) with AutoGenerate flags unless the user has already defined them.

func (*Entity) GetFields

func (e *Entity) GetFields() []schema.Field

GetFields returns the entity's field definitions.

func (*Entity) GetName

func (e *Entity) GetName() string

GetName returns the entity name.

func (*Entity) GetTable

func (e *Entity) GetTable() string

GetTable returns the DB table name.

func (*Entity) Schema

func (e *Entity) Schema() schema.Schema

Schema returns a core/schema.Schema built from the entity's fields.

func (*Entity) SetDB

func (e *Entity) SetDB(db *sql.DB)

SetDB sets the database connection for this entity.

func (*Entity) String

func (e *Entity) String() string

String implements fmt.Stringer.

func (*Entity) Validate

func (e *Entity) Validate() error

Validate checks that the entity config is well-formed.

type EntityConfig

type EntityConfig struct {
	Name         string         // entity name (e.g. "users")
	Table        string         // DB table name (defaults to snake_case of Name)
	Fields       []schema.Field // typed field definitions
	Relations    []Relation     // entity relationships
	Endpoints    []Endpoint     // custom HTTP endpoints for this entity
	SoftDelete   bool           // enable soft-delete (deleted_at column)
	MultiTenant  bool           // scope queries by the tenant column (see TenantField)
	TenantField  string         // tenant-scoping column name when MultiTenant; defaults to "tenant_id"
	Timestamps   bool           // add created_at / updated_at columns
	CRUD         *bool          // auto-generate CRUD routes. nil=auto(true when DB set), &true=always, &false=never
	MCP          bool           // auto-generate MCP tools
	CursorField  string         // optional: single-field keyset cursor; defaults to PrimaryKey
	CursorFields []string       // optional: composite cursor — ORDER BY each field in order with tuple-compared keyset. Wins over CursorField when non-empty.
	Indices      []Index        // additional CREATE INDEX statements emitted by AutoMigrate
	Unmanaged    bool           // when true, the migration system never emits DDL for this object (it is created elsewhere — e.g. a view, an FTS virtual table, or a legacy/external table). The ORM still queries it.
	Properties   map[string]any // caller-owned metadata for generators, plugins, and app conventions
	MaxListLimit int            // opt-in cap for ?limit and the streaming list path. 0 = use default (100); negative = no streaming cap above default.

	// OwnerField names the DB column that holds the row's owner id (e.g.
	// "user_id"). When set AND an owner extractor is registered (typically
	// by battery/auth), auto-CRUD scopes List/Get/Update/Delete by the
	// current request's owner and auto-stamps Create. Leave empty to keep
	// pre-existing behaviour.
	OwnerField string

	// Access declares the RBAC permission required for each CRUD operation.
	// A blank permission leaves that operation un-gated by RBAC (owner and
	// tenant scoping still apply). When set, auto-CRUD refuses a request
	// whose context lacks the permission with 403. Roles + policy must be
	// present in the request context — wire them once with access.Middleware
	// (or battery/auth). See framework/docs/content/access-control.md.
	Access AccessControl

	// Seed runs once per entity after AutoMigrate creates the table. The
	// framework tracks completion in the _gofastr_seeded ledger; subsequent
	// App.Start() calls skip the entity. Errors abort App.Start.
	//
	// Go-only: function values cannot be expressed in JSON entity
	// declarations. Apps that load entities via EntityFromFile /
	// EntitiesFromDir must wire seeding from Go after loading.
	//
	// Concurrency: RunSeeds is NOT safe for concurrent invocation across
	// multiple processes. The framework assumes serialized startup (one
	// process / replica calls App.Start at a time). For HA setups, gate
	// seeding behind an external mechanism (init container, one-shot
	// job, advisory lock). Seed implementations should be idempotent
	// (INSERT … ON CONFLICT DO NOTHING) so accidental re-runs cannot
	// duplicate data.
	Seed func(ctx context.Context, db *sql.DB) error

	// SeedFS is an optional fs.FS (typically a //go:embed embed.FS) that
	// the framework attaches to the Seed function's context. Use with
	// SeedPath to point at a single file within the FS.
	//
	// Go-only: like Seed itself, an fs.FS cannot be expressed in JSON
	// entity declarations.
	SeedFS fs.FS

	// SeedPath is the path within SeedFS that Seed should consume.
	// Ignored when SeedFS is nil.
	SeedPath string
	// contains filtered or unexported fields
}

EntityConfig holds the declarative configuration for an entity. Name is set via Define(); Fields declare the schema. Timestamps defaults to true — use WithTimestamps(false) to disable.

func (EntityConfig) TenantColumn

func (c EntityConfig) TenantColumn() string

TenantColumn returns the tenant-scoping column name for this entity: TenantField when set, otherwise the framework default "tenant_id". This is the single source of the column name across injection, auto-migrate, and the CRUD insert/scope/filter paths.

func (EntityConfig) WithTimestamps

func (c EntityConfig) WithTimestamps(v bool) EntityConfig

WithTimestamps returns a copy of the config with Timestamps set to the given value. Use this to opt out of the default (true).

type EntityDeclaration

type EntityDeclaration struct {
	Name        string             `json:"name"`
	Table       string             `json:"table,omitempty"`
	Fields      []FieldDeclaration `json:"fields"`
	Relations   []Relation         `json:"relations,omitempty"`
	Endpoints   []Endpoint         `json:"endpoints,omitempty"`
	SoftDelete  bool               `json:"soft_delete,omitempty"`
	MultiTenant bool               `json:"multi_tenant,omitempty"`
	// OwnerField names the DB column that holds the row's owner id
	// (e.g. "user_id"). When set AND an owner extractor is registered
	// by a battery, auto-CRUD scopes List/Get/Update/Delete by the
	// current request's owner and auto-stamps Create. Mirrors
	// EntityConfig.OwnerField; leave empty to keep pre-existing behaviour.
	OwnerField   string         `json:"owner_field,omitempty"`
	Timestamps   *bool          `json:"timestamps,omitempty"`
	CRUD         *bool          `json:"crud,omitempty"`
	MCP          bool           `json:"mcp,omitempty"`
	CursorField  string         `json:"cursor_field,omitempty"`
	CursorFields []string       `json:"cursor_fields,omitempty"`
	Indices      []Index        `json:"indices,omitempty"`
	Properties   map[string]any `json:"properties,omitempty"`
}

EntityDeclaration is the JSON shape accepted by EntityFromFile and the CLI code generator. It mirrors EntityConfig while keeping field types readable.

func LoadEntityDeclaration

func LoadEntityDeclaration(path string) (EntityDeclaration, error)

LoadEntityDeclaration reads and validates one entity declaration file.

func LoadEntityDeclarations

func LoadEntityDeclarations(dir string) ([]EntityDeclaration, error)

LoadEntityDeclarations reads all *.json declarations in dir in stable order.

func (EntityDeclaration) Config

func (d EntityDeclaration) Config() (EntityConfig, error)

Config converts a declaration into an EntityConfig.

type FieldDeclaration

type FieldDeclaration struct {
	Name         string   `json:"name"`
	Type         string   `json:"type"`
	Required     bool     `json:"required,omitempty"`
	Unique       bool     `json:"unique,omitempty"`
	Default      any      `json:"default,omitempty"`
	AutoGenerate string   `json:"auto_generate,omitempty"`
	ReadOnly     bool     `json:"read_only,omitempty"`
	Hidden       bool     `json:"hidden,omitempty"`
	Max          *float64 `json:"max,omitempty"`
	Min          *float64 `json:"min,omitempty"`
	Pattern      string   `json:"pattern,omitempty"`
	Values       []string `json:"values,omitempty"`
	To           string   `json:"to,omitempty"`
	Many         bool     `json:"many,omitempty"`
}

FieldDeclaration is a JSON-friendly schema.Field.

func (FieldDeclaration) Field

func (fd FieldDeclaration) Field() (schema.Field, error)

Field converts a JSON field declaration into schema.Field.

type FloatColumn

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

FloatColumn represents a REAL/DOUBLE PRECISION/DECIMAL column.

func NewFloatColumn

func NewFloatColumn(name string) FloatColumn

func (FloatColumn) Asc

func (c FloatColumn) Asc() Order

func (FloatColumn) Desc

func (c FloatColumn) Desc() Order

func (FloatColumn) Eq

func (c FloatColumn) Eq(v float64) Condition

func (FloatColumn) Gt

func (c FloatColumn) Gt(v float64) Condition

func (FloatColumn) Gte

func (c FloatColumn) Gte(v float64) Condition

func (FloatColumn) IsNotNull

func (c FloatColumn) IsNotNull() Condition

func (FloatColumn) IsNull

func (c FloatColumn) IsNull() Condition

func (FloatColumn) Lt

func (c FloatColumn) Lt(v float64) Condition

func (FloatColumn) Lte

func (c FloatColumn) Lte(v float64) Condition

func (FloatColumn) Neq

func (c FloatColumn) Neq(v float64) Condition

type Index

type Index struct {
	Name       string   `json:"name,omitempty"`
	Columns    []string `json:"columns,omitempty"`
	Unique     bool     `json:"unique,omitempty"`
	Expression string   `json:"expression,omitempty"`
}

Index declares a secondary index on an entity. Both dialects accept the same CREATE INDEX syntax; AutoMigrate emits CREATE INDEX IF NOT EXISTS so re-runs are safe.

Name is optional — when empty, AutoMigrate synthesises one as "idx_<table>_<col1>_<col2>". Unique indices reject duplicate rows for the chosen column set; for single-column uniqueness prefer the Field-level Unique flag which lives on the column definition.

Expression covers the case the column-list form can't express: a functional or partial index, e.g. `UNIQUE(user_id, lower(food))` to dedupe case-insensitively. When non-empty, Expression is rendered verbatim inside the index body (replacing Columns) — Name is REQUIRED in that case because there's no safe deterministic slug for an arbitrary expression. Use Columns for plain identifier indices; reach for Expression when SQL functions or constants need to participate in the indexed key.

type IntColumn

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

IntColumn represents an INTEGER column.

func NewIntColumn

func NewIntColumn(name string) IntColumn

func (IntColumn) Asc

func (c IntColumn) Asc() Order

func (IntColumn) Desc

func (c IntColumn) Desc() Order

func (IntColumn) Eq

func (c IntColumn) Eq(v int) Condition

func (IntColumn) Gt

func (c IntColumn) Gt(v int) Condition

func (IntColumn) Gte

func (c IntColumn) Gte(v int) Condition

func (IntColumn) In

func (c IntColumn) In(values ...int) Condition

func (IntColumn) IsNotNull

func (c IntColumn) IsNotNull() Condition

func (IntColumn) IsNull

func (c IntColumn) IsNull() Condition

func (IntColumn) Lt

func (c IntColumn) Lt(v int) Condition

func (IntColumn) Lte

func (c IntColumn) Lte(v int) Condition

func (IntColumn) Neq

func (c IntColumn) Neq(v int) Condition

type Order

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

Order is an order-by-clause fragment.

func (Order) Apply

func (o Order) Apply(qb *query.QueryBuilder)

Apply appends this order to the query builder.

type Registry

type Registry interface {
	// All returns a snapshot of every registered entity keyed by name.
	// Map iteration order is randomised by Go; for stable iteration use
	// AllSorted().
	All() map[string]*Entity

	// AllSorted returns every registered entity in alphabetical order
	// by name. Use this when emitting bytes whose ordering matters
	// (OpenAPI, generated code, golden-file tests, ETag-cached
	// responses).
	AllSorted() []*Entity

	// Get retrieves one entity by name, or an error when no such entity
	// is registered.
	Get(name string) (*Entity, error)
}

Registry is the minimal contract subpackages need from the framework's entity registry: enumerate every registered entity.

All() returns the entities keyed by name. Go's map iteration is randomised, so callers that emit order-sensitive output (OpenAPI tags, LLM markdown, generated code) must use AllSorted() to keep output stable across runs. Callers that only care about presence (counts, hash lookups, contains-checks) can use All() directly.

The concrete *framework.Registry type satisfies this implicitly. Splitting it out here lets framework/migrate, framework/dsl, and others depend on the entity model without pulling in the full framework package.

type Relation

type Relation struct {
	Type             RelationType `json:"type"`
	Name             string       `json:"name"`                // logical name for this relation (e.g. "author", "comments")
	Entity           string       `json:"entity"`              // target entity/table name
	ForeignKey       string       `json:"foreign_key"`         // FK column name
	Through          string       `json:"through,omitempty"`   // pivot table name (ManyToMany only)
	LocalKey         string       `json:"local_key,omitempty"` // column on the local side of a ManyToMany pivot
	ForeignKeyTarget string       `json:"foreign_key_target,omitempty"`
}

Relation describes a relationship between two entities.

func BelongsTo

func BelongsTo(name, ent, foreignKey string) Relation

BelongsTo declares a many-to-one relationship. The source entity holds a foreign-key column that references the target entity's primary key.

func HasMany

func HasMany(name, ent, foreignKey string) Relation

HasMany declares a one-to-many relationship. The target entity holds a foreign-key column that references the source entity's primary key.

func HasOne

func HasOne(name, ent, foreignKey string) Relation

HasOne declares a one-to-one relationship. The target entity holds a foreign-key column that references the source entity's primary key.

func ManyToMany

func ManyToMany(name, ent, throughTable, sourceFK, targetFK string) Relation

ManyToMany declares a many-to-many relationship through a pivot/join table.

type RelationType

type RelationType int

RelationType enumerates the kinds of entity relationships.

const (
	RelHasOne     RelationType = iota // target has a FK pointing back to us
	RelHasMany                        // target has a FK pointing back to us (many rows)
	RelManyToOne                      // we hold a FK pointing to the target (BelongsTo)
	RelManyToMany                     // linked through a pivot/join table
)

type StringColumn

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

StringColumn represents a TEXT/VARCHAR column. Use the methods to build Conditions: PostsTitle.Eq("hello"), PostsTitle.Like("%foo%"), etc.

func NewStringColumn

func NewStringColumn(name string) StringColumn

NewStringColumn constructs a StringColumn for the given DB column name. Codegen calls this; user code rarely needs to.

func (StringColumn) Asc

func (c StringColumn) Asc() Order

func (StringColumn) Desc

func (c StringColumn) Desc() Order

func (StringColumn) Eq

func (c StringColumn) Eq(v string) Condition

func (StringColumn) In

func (c StringColumn) In(values ...string) Condition

func (StringColumn) IsNotNull

func (c StringColumn) IsNotNull() Condition

func (StringColumn) IsNull

func (c StringColumn) IsNull() Condition

func (StringColumn) Like

func (c StringColumn) Like(pattern string) Condition

func (StringColumn) Neq

func (c StringColumn) Neq(v string) Condition

func (StringColumn) NotLike

func (c StringColumn) NotLike(pattern string) Condition

type TimestampColumn

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

TimestampColumn represents a TIMESTAMP/TIMESTAMPTZ column. Method semantics mirror IntColumn but accept any value the driver knows how to bind (time.Time, RFC3339 strings, etc.) so callers don't have to choose a canonical form here.

func NewTimestampColumn

func NewTimestampColumn(name string) TimestampColumn

func (TimestampColumn) Asc

func (c TimestampColumn) Asc() Order

func (TimestampColumn) Desc

func (c TimestampColumn) Desc() Order

func (TimestampColumn) Eq

func (c TimestampColumn) Eq(v any) Condition

func (TimestampColumn) Gt

func (c TimestampColumn) Gt(v any) Condition

func (TimestampColumn) Gte

func (c TimestampColumn) Gte(v any) Condition

func (TimestampColumn) IsNotNull

func (c TimestampColumn) IsNotNull() Condition

func (TimestampColumn) IsNull

func (c TimestampColumn) IsNull() Condition

func (TimestampColumn) Lt

func (c TimestampColumn) Lt(v any) Condition

func (TimestampColumn) Lte

func (c TimestampColumn) Lte(v any) Condition

type UUIDColumn

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

UUIDColumn represents a UUID/text-shaped identity column.

func NewUUIDColumn

func NewUUIDColumn(name string) UUIDColumn

func (UUIDColumn) Asc

func (c UUIDColumn) Asc() Order

func (UUIDColumn) Desc

func (c UUIDColumn) Desc() Order

func (UUIDColumn) Eq

func (c UUIDColumn) Eq(v string) Condition

func (UUIDColumn) In

func (c UUIDColumn) In(values ...string) Condition

func (UUIDColumn) IsNotNull

func (c UUIDColumn) IsNotNull() Condition

func (UUIDColumn) IsNull

func (c UUIDColumn) IsNull() Condition

func (UUIDColumn) Neq

func (c UUIDColumn) Neq(v string) Condition

type ValidationRegistry

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

ValidationRegistry holds a chain of validator functions.

func NewValidationRegistry

func NewValidationRegistry() *ValidationRegistry

NewValidationRegistry creates an empty ValidationRegistry.

func (*ValidationRegistry) RegisterValidator

func (vr *ValidationRegistry) RegisterValidator(fn ValidatorFunc)

RegisterValidator appends a validator function to the chain.

func (*ValidationRegistry) Validate

func (vr *ValidationRegistry) Validate(ctx context.Context, data map[string]any) map[string]string

Validate runs all registered validators and collects every field error. The returned map is field name → error message. A nil/empty map means valid.

func (*ValidationRegistry) Validators

func (vr *ValidationRegistry) Validators() int

Validators returns the number of registered validators (for testing).

type ValidatorFunc

type ValidatorFunc func(ctx context.Context, data map[string]any) map[string]string

ValidatorFunc validates entity data and returns field-level errors. The returned map is field name → error message (empty map means valid).

func Custom

func Custom(name string, fn func(ctx context.Context, data map[string]any) map[string]string) ValidatorFunc

Custom returns a validator with a given name that runs the provided function. The fn returns a map of field→error for any violations found.

func Required

func Required(fields ...string) ValidatorFunc

Required returns a validator that checks the given fields are present and non-zero.

func Unique

func Unique(field string, checkFn func(ctx context.Context, value any) bool) ValidatorFunc

Unique returns a validator that checks a field value is unique using the provided check function. The checkFn receives the field value and returns true if the value is unique (not taken).

Jump to

Keyboard shortcuts

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