Documentation
¶
Index ¶
- func FormatValidationErrors(errors map[string]string) []string
- func SeedDataFromContext(ctx context.Context) ([]byte, error)
- func WithSeedDataContext(ctx context.Context, sfs fs.FS, path string) context.Context
- type AccessControl
- type BoolColumn
- type Condition
- type Endpoint
- type Entity
- type EntityConfig
- type EntityDeclaration
- type FieldDeclaration
- type FloatColumn
- func (c FloatColumn) Asc() Order
- func (c FloatColumn) Desc() Order
- func (c FloatColumn) Eq(v float64) Condition
- func (c FloatColumn) Gt(v float64) Condition
- func (c FloatColumn) Gte(v float64) Condition
- func (c FloatColumn) IsNotNull() Condition
- func (c FloatColumn) IsNull() Condition
- func (c FloatColumn) Lt(v float64) Condition
- func (c FloatColumn) Lte(v float64) Condition
- func (c FloatColumn) Neq(v float64) Condition
- type Index
- type IntColumn
- func (c IntColumn) Asc() Order
- func (c IntColumn) Desc() Order
- func (c IntColumn) Eq(v int) Condition
- func (c IntColumn) Gt(v int) Condition
- func (c IntColumn) Gte(v int) Condition
- func (c IntColumn) In(values ...int) Condition
- func (c IntColumn) IsNotNull() Condition
- func (c IntColumn) IsNull() Condition
- func (c IntColumn) Lt(v int) Condition
- func (c IntColumn) Lte(v int) Condition
- func (c IntColumn) Neq(v int) Condition
- type Order
- type Registry
- type Relation
- type RelationType
- type StringColumn
- func (c StringColumn) Asc() Order
- func (c StringColumn) Desc() Order
- func (c StringColumn) Eq(v string) Condition
- func (c StringColumn) In(values ...string) Condition
- func (c StringColumn) IsNotNull() Condition
- func (c StringColumn) IsNull() Condition
- func (c StringColumn) Like(pattern string) Condition
- func (c StringColumn) Neq(v string) Condition
- func (c StringColumn) NotLike(pattern string) Condition
- type TimestampColumn
- func (c TimestampColumn) Asc() Order
- func (c TimestampColumn) Desc() Order
- func (c TimestampColumn) Eq(v any) Condition
- func (c TimestampColumn) Gt(v any) Condition
- func (c TimestampColumn) Gte(v any) Condition
- func (c TimestampColumn) IsNotNull() Condition
- func (c TimestampColumn) IsNull() Condition
- func (c TimestampColumn) Lt(v any) Condition
- func (c TimestampColumn) Lte(v any) Condition
- type UUIDColumn
- type ValidationRegistry
- type ValidatorFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatValidationErrors ¶
FormatValidationErrors formats a map of field errors into a user-friendly string slice.
func SeedDataFromContext ¶
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 ¶
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 ¶
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) Eq ¶
func (c BoolColumn) Eq(v bool) Condition
func (BoolColumn) IsFalse ¶
func (c BoolColumn) IsFalse() 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 ¶
And combines conditions with AND. Useful inside Or(...) to nest a group of ANDed predicates: Or(And(a, b), And(c, d)).
func Or ¶
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.
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"`
InputSchema []schema.Field `json:"inputSchema,omitempty"`
OutputSchema []schema.Field `json:"outputSchema,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.
InputSchema and OutputSchema are OPTIONAL typed descriptions of the request body and the success (200) response, expressed as []schema.Field — the same representation the entity's own CRUD schema is built from, so OpenAPI and the generated MCP tool both consume one source. When unset (nil), the endpoint renders exactly as before: a shapeless {type:object} request/response in OpenAPI and a {type:object} MCP tool input schema. InputSchema is ignored for GET endpoints (which carry no request body).
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.
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 a blueprint
// declaration. Apps whose entities come from a gofastr.yml blueprint
// must wire seeding from Go.
//
// 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 readable, JSON/YAML-friendly shape the gofastr.yml blueprint loader decodes entities into before converting them to EntityConfig. It mirrors EntityConfig while keeping field types readable.
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.
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) 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) 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) Eq ¶
func (c StringColumn) Eq(v string) Condition
func (StringColumn) In ¶
func (c StringColumn) In(values ...string) 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) 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) 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) Eq ¶
func (c UUIDColumn) Eq(v string) Condition
func (UUIDColumn) In ¶
func (c UUIDColumn) In(values ...string) 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 ¶
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 ¶
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.