crud

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: 36 Imported by: 0

Documentation

Index

Constants

View Source
const MaxBatchSize = 100

MaxBatchSize caps how many items a single _batch request may contain. The cap exists to bound transaction duration and protect against pathological payloads.

View Source
const MaxJSONBodyBytes int64 = 1 << 20 // 1 MiB

MaxJSONBodyBytes caps the size of a JSON request body the CRUD handlers will read. 1 MiB is large enough for any realistic single record or batch envelope, while bounding the memory an unauthenticated caller can pin per request.

View Source
const MaxMultipartMemory = 32 << 20 // 32 MiB

MaxMultipartMemory is the in-memory portion of a multipart upload before spilling to disk. Files larger than this still upload — they just stream through a temp file.

Variables

This section is empty.

Functions

func AuditPreImageFromContext

func AuditPreImageFromContext(ctx context.Context) map[string]any

AuditPreImageFromContext returns the pre-image set by doUpdate or doDelete, or nil when no snapshot was captured (legacy callers / async hooks).

func AuditRequestFromContext

func AuditRequestFromContext(ctx context.Context) *http.Request

AuditRequestFromContext returns the *http.Request that CRUD attached, or nil if none was set (e.g. async hook fired outside a request).

func EagerLoad

func EagerLoad(ctx context.Context, db DBExecutor, ent *entity.Entity, relations []entity.Relation, ids []string, reg ...entity.Registry) (map[string]map[string]any, error)

EagerLoad fetches related data for a set of parent IDs in batched queries, avoiding N+1 problems. Returns a map keyed by parent ID to relation name to related data.

SECURITY: when an optional entity.Registry is supplied, each relation's target entity is resolved and the same scrubbing the live include path applies (eager_filtered.go) is applied here too — soft-deleted target rows are excluded (`deleted_at IS NULL`) and Hidden columns (e.g. password_hash) are never populated. Without a registry the target schema is unknown, so no per-target scrub can be applied; callers that load relations whose targets are soft-deletable or carry Hidden fields MUST pass the registry.

func EntityLLMMD

func EntityLLMMD(ent *entity.Entity) string

EntityLLMMD generates an LLM-friendly markdown document describing all CRUD endpoints for a single entity. The output is designed to be immediately useful as context for an LLM agent — concise, structured, and example-rich.

The document covers:

  • Resource overview (entity name, table, primary key)
  • List endpoint with filter operators, pagination (offset + cursor), includes
  • Get by ID with includes
  • Create with required/writable fields
  • Update with writable fields
  • Delete
  • Batch create / update / delete
  • SSE event stream
  • Custom endpoints declared on the entity

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err corresponds to a not-found result on a typed CRUD operation. Treats sql.ErrNoRows and the framework's internal errNotFound (from Update/Delete misses) as equivalent.

func LLMMDHandler

func LLMMDHandler(ent *entity.Entity) http.Handler

LLMMDHandler returns an http.Handler that serves the LLM-friendly markdown for a single entity. The schema-disclosure surface is broad (every field, validator, relation), so the handler requires an authenticated context — the framework's auth chain must have set a user before this fires.

func MarshalEntity

func MarshalEntity(src any) (map[string]any, error)

MarshalEntity is the inverse — converts a typed entity struct into the snake-cased map[string]any the framework's CRUD primitives expect. Skips fields whose JSON tag carries omitempty if the value is the type's zero, per encoding/json semantics.

func NormalizePath

func NormalizePath(path string) string

NormalizePath strips trailing slashes from a path.

func RegisterCrudRoutes

func RegisterCrudRoutes(r *router.Router, handler *CrudHandler, path string, opts ...CrudRouteOptions)

RegisterCrudRoutes registers the standard CRUD routes plus batch endpoints on the given router.

GET    /path             → List
GET    /path/{id}        → Get
POST   /path             → Create
PUT    /path/{id}        → Update
DELETE /path/{id}        → Delete
POST   /path/_batch      → BatchCreate (atomic; all-or-nothing)
PATCH  /path/_batch      → BatchUpdate (atomic; all-or-nothing)
DELETE /path/_batch      → BatchDelete (atomic; all-or-nothing)

The batch routes use a "_batch" segment, which Go 1.22's ServeMux ranks above the wildcard /{id} so they take precedence over Get/Update/Delete on the same prefix.

func RegisterEntityMCPTools

func RegisterEntityMCPTools(server *mcp.Server, crud *CrudHandler, router http.Handler) error

RegisterEntityMCPTools exposes a CRUD handler through MCP tools.

router is the http.Handler that owns the entity's CRUD routes — typically app.Router. MCP tool calls are dispatched through router.ServeHTTP so they share the exact same middleware chain (auth, recovery, logging, security headers, etc.) as live HTTP traffic. Passing the bare CRUD handler would silently bypass that middleware.

func RegistryLLMMD

func RegistryLLMMD(registry entity.Registry, appName string) string

RegistryLLMMD generates a top-level LLM-friendly markdown index that lists every registered entity with a link to its detailed llm.md page.

func RegistryLLMMDHandler

func RegistryLLMMDHandler(registry entity.Registry, appName string) http.Handler

RegistryLLMMDHandler returns an http.Handler that serves the top-level LLM-friendly markdown index for all registered entities. Auth-required for the same reason as LLMMDHandler.

func UnmarshalEntity

func UnmarshalEntity(m map[string]any, dest any) error

UnmarshalEntity is the public entry point for converting a snake-cased row map into a typed entity struct (json tags in camelCase). Exposed so generated repository code outside this package can call it.

func WithAuditPreImage

func WithAuditPreImage(ctx context.Context, row map[string]any) context.Context

WithAuditPreImage stores the pre-change row snapshot in ctx so the AfterUpdate / AfterDelete hooks can diff it against the new state. doUpdate / doDelete populate this with a freshly SELECTed copy before the mutating statement runs.

func WithAuditRequest

func WithAuditRequest(ctx context.Context, r *http.Request) context.Context

WithAuditRequest returns ctx with r attached so audit hooks can read client-IP / user-agent / headers in their AfterCreate/Update/Delete callbacks. CRUD's HTTP entry points call this before any DB work.

Types

type BatchResponse

type BatchResponse struct {
	Committed bool          `json:"committed"`
	Results   []batchResult `json:"results"`
}

BatchResponse is the standard envelope for all _batch endpoints.

type CrudHandler

type CrudHandler struct {
	Entity     *entity.Entity
	DB         DBExecutor
	PrimaryKey string             // defaults to "id"
	JSONCase   JSONCase           // casing strategy for JSON keys
	Hooks      *hook.HookRegistry // optional lifecycle hooks
	Storage    upload.Storage     // optional; enables multipart uploads for Image/File fields
	Events     *event.EventBus    // optional; receives entity.created/updated/deleted on commit
	Registry   entity.Registry    // optional; required for nested ?include=author.profile resolution
	BasePath   string             // optional; URL prefix where this entity's routes are mounted (e.g. "/api/v1"). Used by MCP tools to dispatch against the same path the HTTP routes live at; empty = bare "/table".
	// contains filtered or unexported fields
}

CrudHandler provides auto-generated CRUD HTTP handlers for an Entity.

func NewCrudHandler

func NewCrudHandler(ent *entity.Entity, db DBExecutor) *CrudHandler

NewCrudHandler creates a new CrudHandler for the given entity and database.

func RegisterCrudRoutesFunc

func RegisterCrudRoutesFunc(r *router.Router, ent *entity.Entity, db DBExecutor, path string, opts ...CrudRouteOptions) *CrudHandler

RegisterCrudRoutesFunc is a convenience that creates a CrudHandler and registers routes in one call.

func (*CrudHandler) ApplyOwnerScope

func (ch *CrudHandler) ApplyOwnerScope(qb *query.QueryBuilder, r *http.Request)

ApplyOwnerScope adds an `<owner_field> = ?` predicate to a SELECT query when the entity declares OwnerField and the request context carries an owner id (registered via framework/owner.SetExtractor — typically by battery/auth's init()). No-op when either condition is missing.

Uses PostgreSQL-style $N placeholders, matching ApplyTenantScope.

func (*CrudHandler) ApplyOwnerScopeCount

func (ch *CrudHandler) ApplyOwnerScopeCount(cb *query.CountBuilder, r *http.Request)

ApplyOwnerScopeCount mirrors ApplyOwnerScope for count queries.

func (*CrudHandler) ApplyOwnerScopeDelete

func (ch *CrudHandler) ApplyOwnerScopeDelete(db *query.DeleteBuilder, r *http.Request)

ApplyOwnerScopeDelete mirrors ApplyOwnerScope for DELETE queries.

func (*CrudHandler) ApplyOwnerScopeUpdate

func (ch *CrudHandler) ApplyOwnerScopeUpdate(ub *query.UpdateBuilder, r *http.Request)

ApplyOwnerScopeUpdate mirrors ApplyOwnerScope for UPDATE queries.

func (*CrudHandler) ApplySoftDeleteFilter

func (ch *CrudHandler) ApplySoftDeleteFilter(qb *query.QueryBuilder, r *http.Request)

ApplySoftDeleteFilter adds a deleted_at IS NULL filter unless the caller requests trashed records via ?trashed=true AND the request is authenticated. An anonymous caller passing ?trashed=true on a public list endpoint must not be allowed to enumerate soft-deleted rows — that's an information-disclosure path. The query param is honoured only when a user is present in the request context.

func (*CrudHandler) ApplySoftDeleteFilterCount

func (ch *CrudHandler) ApplySoftDeleteFilterCount(cb *query.CountBuilder, r *http.Request)

ApplySoftDeleteFilterCount adds a deleted_at IS NULL filter to a count query. Same authentication gate as ApplySoftDeleteFilter.

func (*CrudHandler) ApplyTenantScope

func (ch *CrudHandler) ApplyTenantScope(qb *query.QueryBuilder, r *http.Request)

ApplyTenantScope adds a tenant_id filter to the query when the entity is configured for multi-tenancy and a tenant ID is present in the context. Note: uses PostgreSQL-style $1 placeholders.

func (*CrudHandler) ApplyTenantScopeCount

func (ch *CrudHandler) ApplyTenantScopeCount(cb *query.CountBuilder, r *http.Request)

ApplyTenantScopeCount adds a tenant_id filter to a count query builder. Note: uses PostgreSQL-style $1 placeholders.

func (*CrudHandler) ApplyTenantScopeDelete

func (ch *CrudHandler) ApplyTenantScopeDelete(db *query.DeleteBuilder, r *http.Request)

ApplyTenantScopeDelete adds a tenant_id filter to a delete query builder. Note: uses PostgreSQL-style $1 placeholders.

func (*CrudHandler) ApplyTenantScopeUpdate

func (ch *CrudHandler) ApplyTenantScopeUpdate(ub *query.UpdateBuilder, r *http.Request)

ApplyTenantScopeUpdate adds a tenant_id filter to an update query builder. Note: uses PostgreSQL-style $1 placeholders.

func (*CrudHandler) BatchCreate

func (ch *CrudHandler) BatchCreate() http.HandlerFunc

BatchCreate returns an http.HandlerFunc accepting POST /{table}/_batch with {items:[...]}. All items run in one transaction; the first per-item error rolls everything back. The response always includes a results array in input order.

func (*CrudHandler) BatchCreateMany

func (ch *CrudHandler) BatchCreateMany(ctx context.Context, bodies []map[string]any) ([]map[string]any, error)

BatchCreateMany runs CreateOne for each body in a single transaction. Events fire after commit (per item, in input order). Any per-item error rolls back the whole batch — same semantics as the HTTP _batch endpoint.

func (*CrudHandler) BatchDelete

func (ch *CrudHandler) BatchDelete() http.HandlerFunc

BatchDelete returns an http.HandlerFunc accepting DELETE /{table}/_batch with {ids:[...]}. All deletes share one transaction.

func (*CrudHandler) BatchDeleteMany

func (ch *CrudHandler) BatchDeleteMany(ctx context.Context, ids []string) ([]string, error)

BatchDeleteMany deletes (or soft-deletes) each id atomically. Returns the ids that were successfully removed.

func (*CrudHandler) BatchUpdate

func (ch *CrudHandler) BatchUpdate() http.HandlerFunc

BatchUpdate returns an http.HandlerFunc accepting PATCH /{table}/_batch. Each item must contain an "id" field naming the record to patch; remaining fields are the partial update. All items share one transaction.

func (*CrudHandler) BatchUpdateMany

func (ch *CrudHandler) BatchUpdateMany(ctx context.Context, ids []string, bodies []map[string]any) ([]map[string]any, error)

BatchUpdateMany runs UpdateOne for each (id, body) pair atomically.

func (*CrudHandler) CountAll

func (ch *CrudHandler) CountAll(ctx context.Context, opts ListOptions) (int, error)

CountAll returns COUNT(*) for the given filters (no pagination). Cheap helper for typed repos that want a totals figure without hitting List.

func (*CrudHandler) Create

func (ch *CrudHandler) Create() http.HandlerFunc

Create returns an http.HandlerFunc that creates a new entity record. Auto-generated fields are populated server-side and excluded from the request body. The hook chain (BeforeCreate → INSERT → AfterCreate) runs inside a single transaction; if any step errors the write is rolled back.

Accepts application/json or multipart/form-data. When the request is multipart, parts whose name matches an Image/File field are streamed through the handler's Storage backend and persisted as a URL string.

func (*CrudHandler) CreateOne

func (ch *CrudHandler) CreateOne(ctx context.Context, body map[string]any) (map[string]any, error)

CreateOne runs the full Create pipeline (BeforeCreate hooks → INSERT → AfterCreate hooks) inside a single transaction and emits entity.created on commit. Returns the created row as a snake_cased map; convert to a typed struct in your caller.

func (*CrudHandler) Delete

func (ch *CrudHandler) Delete() http.HandlerFunc

Delete returns an http.HandlerFunc that deletes an entity by ID. If the entity has SoftDelete=true, it sets deleted_at instead. The hook chain (BeforeDelete → DELETE/UPDATE → AfterDelete) runs inside a transaction.

func (*CrudHandler) DeleteOne

func (ch *CrudHandler) DeleteOne(ctx context.Context, id string) error

DeleteOne deletes (or soft-deletes) a record by id.

func (*CrudHandler) EmitEvent

func (ch *CrudHandler) EmitEvent(ctx context.Context, eventType string, record any)

EmitEvent fires an entity lifecycle event (asynchronously). No-op when the handler has no event bus attached. The payload is shaped so SSE subscribers can filter by entity name and tenant without unmarshalling the record.

func (*CrudHandler) EventStream

func (ch *CrudHandler) EventStream() http.HandlerFunc

EventStream returns an http.HandlerFunc that serves a Server-Sent Events stream of EntityCreated / EntityUpdated / EntityDeleted events scoped to this entity. When the entity is multi-tenant, events are further filtered to the tenant ID extracted from the request context.

Each accepted event is written as:

event: entity.created (or entity.updated / entity.deleted)
data:  {<full Event JSON>}

Disconnects from the client unsubscribe automatically. A backpressure buffer of 32 is enforced — if the client cannot keep up, events are dropped rather than blocking emitters.

func (*CrudHandler) Get

func (ch *CrudHandler) Get() http.HandlerFunc

Get returns an http.HandlerFunc that fetches a single entity by ID. Honours ?include= eager-loaded relations.

Hook chain: BeforeGet → SELECT → AfterGet. The BeforeGet hook can append WHERE predicates via *hook.GetPayload.AddWhere to scope the lookup (mismatches return 404). AfterGet may mutate the result map (redact, transform).

func (*CrudHandler) GetOne

func (ch *CrudHandler) GetOne(ctx context.Context, id string, includes []string) (map[string]any, error)

GetOne fetches a single record by id, optionally eager-loading the named includes. Returns sql.ErrNoRows when the record doesn't exist (or is soft-deleted, unless options ask otherwise).

func (*CrudHandler) InjectOwner

func (ch *CrudHandler) InjectOwner(data map[string]any, ctx context.Context)

InjectOwner stamps the owner id into a Create payload when the entity declares OwnerField. Mirrors InjectTenant's shape.

func (*CrudHandler) InjectTenant

func (ch *CrudHandler) InjectTenant(data map[string]any, ctx context.Context)

InjectTenant injects the tenant_id into a data map when multi-tenancy is enabled. It reads the tenant ID from ctx so it works whether the caller is outside or inside an in-tx context derived from the request.

func (*CrudHandler) List

func (ch *CrudHandler) List() http.HandlerFunc

List returns an http.HandlerFunc that lists entity records with filtering, sorting, pagination, and optional ?include= eager-loaded relations.

Hook chain: BeforeList → SELECT → AfterList. The BeforeList hook can append WHERE predicates via the *hook.ListPayload.AddWhere helper; appended clauses apply to both the data query and the count query. AfterList receives the fetched results and may mutate them in place.

func (*CrudHandler) ListAll

func (ch *CrudHandler) ListAll(ctx context.Context, opts ListOptions) ([]map[string]any, error)

ListAll runs a list query with optional filters/sort/limit/offset/includes and returns the matching rows. Caller is responsible for paging if the result set is large.

func (*CrudHandler) RequireOwner

func (ch *CrudHandler) RequireOwner(w http.ResponseWriter, r *http.Request) (id any, ok bool)

RequireOwner returns the current owner id when the entity declares OwnerField. ok=true means: either no owner is required (entity has no OwnerField), or an owner was extracted. ok=false means: the entity requires an owner but none is available — the caller MUST refuse the request. Writes 401 to w and returns ok=false in that case so handlers can `if _, ok := ch.RequireOwner(w, r); !ok { return }`.

This is the secure-by-default seam: without it, ApplyOwnerScope would silently no-op for anonymous requests on OwnerField entities, returning every row in the table. With OwnerField set the framework refuses requests that can't produce an owner id, regardless of whether the caller mounted auth middleware in front of the route.

func (*CrudHandler) RequireTenant

func (ch *CrudHandler) RequireTenant(w http.ResponseWriter, r *http.Request) (ok bool)

RequireTenant is the HTTP mirror of RequireOwner for multi-tenant entities. ok=true means: either the entity is not MultiTenant, or a tenant id is present in the request context. ok=false means the entity is MultiTenant but the request carries no tenant id — the caller MUST refuse the request. Writes 401 to w and returns ok=false in that case so handlers can `if !ch.RequireTenant(w, r) { return }`.

This is the secure-by-default seam matching requireTenantContext (the in-process mirror). Without it, ApplyTenantScope* silently no-op when no tenant is in context, leaking every tenant's rows on read and permitting cross-tenant update/delete-by-id. Hosts that genuinely need cross-tenant access (admin tooling) must set a tenant id deliberately rather than rely on an empty context.

func (*CrudHandler) ServeStreamingList

func (ch *CrudHandler) ServeStreamingList(ctx context.Context, w http.ResponseWriter, r *http.Request, cols []string, filters []filter.ParsedFilter, nested []nestedFilter, sorts []filter.ParsedSort, page, limit int, extraWhere []hook.WhereClause)

ServeStreamingList writes the list response row-by-row through a json.Encoder rather than buffering everything into a slice first. Used for very large pages (limit ≥ streamListThreshold) or when the caller asks for it via ?stream=true.

The wire shape is identical to the regular list envelope so existing clients keep working: {"data": [...], "total": N, "page": P, "perPage": N, "totalPages": T}. Streaming applies only to the data array — the envelope fields are written before the rows start flowing.

`page` is honoured the same way the non-stream List handler honours it: OFFSET (page-1)*limit. Without this, ?page=2&stream=true would re-stream page 1 while reporting page 1 — silently dropping the offset.

func (*CrudHandler) Update

func (ch *CrudHandler) Update() http.HandlerFunc

Update returns an http.HandlerFunc that updates an entity by ID. The hook chain (BeforeUpdate → UPDATE → AfterUpdate) runs inside a transaction. Accepts application/json or multipart/form-data (same rules as Create).

func (*CrudHandler) UpdateOne

func (ch *CrudHandler) UpdateOne(ctx context.Context, id string, body map[string]any) (map[string]any, error)

UpdateOne updates a record by id with the partial body. Hooks + tx + event emission all fire as in the HTTP path.

func (*CrudHandler) UpsertOne

func (ch *CrudHandler) UpsertOne(ctx context.Context, body map[string]any) (map[string]any, error)

UpsertOne performs an INSERT ... ON CONFLICT DO UPDATE on the entity's primary key. If a row with the same PK already exists, every writable field in body overwrites the existing row; otherwise a new row is inserted. Returns the resulting row (post-insert or post-update).

Both Postgres and SQLite 3.24+ support this exact syntax via the EXCLUDED pseudo-table. The framework's BeforeCreate/AfterCreate hooks fire (an upsert that turns into an INSERT is semantically a Create); no Update hooks fire — if you need to distinguish, route through the regular Create/Update endpoints.

func (*CrudHandler) VisibleFields

func (ch *CrudHandler) VisibleFields() []string

VisibleFields returns field names that are not Hidden.

func (*CrudHandler) WithJSONCase

func (ch *CrudHandler) WithJSONCase(c JSONCase) *CrudHandler

WithJSONCase sets the JSON casing strategy for the handler.

type CrudRouteOptions

type CrudRouteOptions struct {
	NoLLMMD  bool // disable auto-generated /path/llm.md
	ReadOnly bool // register only the read routes (List/Get/events) — for views and other read-only objects
}

CrudRouteOptions controls which routes are registered by RegisterCrudRoutes.

type DBExecutor

type DBExecutor = db.Executor

DBExecutor is an alias for db.Executor, retained so existing callers keep using framework.DBExecutor. New code should reference framework/db directly.

type IncludeNode

type IncludeNode struct {
	Name     string                // segment name (matches the relation's Name)
	Relation entity.Relation       // relation declared on the parent entity
	Target   *entity.Entity        // the entity Reached by following Relation
	Filters  []filter.ParsedFilter // scoped filters applied during eager-load
	Children []*IncludeNode        // deeper includes, e.g. for "author.profile" the "profile" child of "author"
	// contains filtered or unexported fields
}

IncludeNode represents one segment of a (possibly nested) ?include= expression. The tree is rooted at the request's entity; each node carries the relation taken to reach it and any deeper child includes.

Filters narrows the eager-load to rows on the target that match every scoped predicate, e.g. include=comments(status=published) attaches only published comments. Suffixes (_gt/_gte/_lt/_lte/_like/_in) work the same way they do for top-level filters.

type JSONCase

type JSONCase string

JSONCase defines the casing convention for JSON keys in API responses.

CaseCamel — camelCase (default, web standard) CaseSnake — snake_case (database-style)

const (
	CaseCamel JSONCase = "camelCase"
	CaseSnake JSONCase = "snake_case"
)

type ListOptions

type ListOptions struct {
	Filters  []filter.ParsedFilter
	Sorts    []filter.ParsedSort
	Limit    int
	Offset   int
	Includes []string
}

ListOptions controls ListAll.

type ListResponse

type ListResponse struct {
	Data       []map[string]any `json:"data"`
	Total      int              `json:"total"`
	Page       int              `json:"page"`
	PerPage    int              `json:"perPage"`
	TotalPages int              `json:"totalPages"`
}

ListResponse is the standard JSON response for list endpoints.

type TypedQuery

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

TypedQuery is a generic, fluent query builder that returns []*T from the underlying CrudHandler's table. Generated typed repositories construct one via NewTypedQuery; the type parameter T is the entity's struct.

The builder wraps the existing core/query.QueryBuilder (so filters, sorts, limits, tenant scope, and soft-delete behave identically to the HTTP path) and runs the same eager-load helpers when Include() is set.

func NewTypedQuery

func NewTypedQuery[T any](h *CrudHandler) *TypedQuery[T]

NewTypedQuery starts a new query against the handler's entity. Callers should chain Where/Order/Limit/Include and finish with Find/First/Count.

func (*TypedQuery[T]) Count

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

Count returns the number of rows matching the current filters. Ignores limit/offset/order — pure SELECT COUNT(*) over the same WHERE predicate.

func (*TypedQuery[T]) DeleteAll

func (q *TypedQuery[T]) DeleteAll(ctx context.Context) (int, error)

DeleteAll removes every row matching the current Where chain. For SoftDelete entities, sets deleted_at instead of issuing a DELETE.

func (*TypedQuery[T]) Exists

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

Exists returns true if at least one row matches the current WHERE chain. Cheaper than First+IsNotFound for the "do any match?" question because it runs a COUNT(*) with LIMIT 1 internally.

func (*TypedQuery[T]) Find

func (q *TypedQuery[T]) Find(ctx context.Context) ([]*T, error)

Find executes the query and decodes results into []*T.

func (*TypedQuery[T]) First

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

First runs the query with Limit(1) and returns the lone result. Returns sql.ErrNoRows when the query yields zero rows so callers can detect "not found" with errors.Is.

func (*TypedQuery[T]) Include

func (q *TypedQuery[T]) Include(rels ...string) *TypedQuery[T]

Include eager-loads the named relations on the result rows. Names follow the same dotted-path syntax as ?include= (e.g. "author.profile").

func (*TypedQuery[T]) Limit

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

Limit caps the result size.

func (*TypedQuery[T]) Offset

func (q *TypedQuery[T]) Offset(n int) *TypedQuery[T]

Offset skips the first n rows.

func (*TypedQuery[T]) Order

func (q *TypedQuery[T]) Order(o entity.Order) *TypedQuery[T]

Order appends an ORDER BY clause. Multiple Order calls compose in input order (first declared = primary sort).

func (*TypedQuery[T]) UpdateAll

func (q *TypedQuery[T]) UpdateAll(ctx context.Context, fields map[string]any) (int, error)

UpdateAll applies the same field updates to every row matching the current Where chain and returns the number of rows touched. Ignores Limit/Offset/Order — the underlying SQL is a plain UPDATE with the same WHERE predicate that Find would use.

Fields are snake-cased map[string]any (the framework's wire shape). For type-safe updates, marshal a partial struct with framework.MarshalEntity and pass the resulting map.

func (*TypedQuery[T]) Where

func (q *TypedQuery[T]) Where(c entity.Condition) *TypedQuery[T]

Where appends an AND condition. Multiple Where calls AND together.

Jump to

Keyboard shortcuts

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