Documentation
¶
Overview ¶
Package data is the headless data spine of the go-widgets ecosystem: a typed record model with validation, a collection with sort/filter/group/pagination/ aggregation, and a pluggable proxy so the very same query pipeline runs in-process (MemoryProxy) or against a remote service (the grpcproxy subpackage) — natively and in a browser/wasm build alike. It imports only the standard library and go-widgets/mvvm, so nothing here depends on a GUI.
Index ¶
- Variables
- func Canonical(v View) []byte
- type Agg
- type AggFunc
- type Codec
- type Field
- type Filter
- type FilterOp
- type Group
- type Kind
- type MemoryProxy
- type Mutation
- type MutationKind
- type Proxy
- type Query
- type Record
- type Rule
- type Schema
- type Sort
- type Store
- func (s *Store[R]) Add(ctx context.Context, row R) error
- func (s *Store[R]) Delete(ctx context.Context, key Value) error
- func (s *Store[R]) Items() *mvvm.ObservableList[R]
- func (s *Store[R]) Load(ctx context.Context) (View, error)
- func (s *Store[R]) Query() Query
- func (s *Store[R]) SetQuery(q Query)
- func (s *Store[R]) Update(ctx context.Context, row R) error
- type Value
- type View
Constants ¶
This section is empty.
Variables ¶
var ( ErrKeyExists = errors.New("data: key already exists") ErrNotFound = errors.New("data: record not found") )
ErrKeyExists / ErrNotFound report the two identity failures a mutation can hit: inserting a key that already exists, or updating/deleting one that does not.
Functions ¶
func Canonical ¶
Canonical renders a View as a deterministic byte string: the same View value always yields the same bytes, and — crucially — a View computed by MemoryProxy and the identical View reconstructed from a grpcproxy round-trip encode to the SAME bytes. That is the equality the proxy-conformance test asserts.
Determinism is achieved by never depending on Go map iteration order: record fields and aggregate labels are always emitted in sorted order, and rows and groups keep their query order. The format is unambiguous (length-tagged enough for the fields we carry), but it is meant for equality, not parsing.
Types ¶
type Codec ¶
Codec converts between a caller's typed row R and the schema-typed Record the proxy stores. Supplying the two functions keeps Store reflection-free and lets the app choose exactly how its struct maps onto fields.
type Field ¶
Field declares one column of a Schema: its name, its scalar Kind, and any validation Rules run against a row's value for it.
type Filter ¶
Filter is one predicate: keep the rows for which Field's cell relates to Value under Op. A filter on a field a row lacks never matches.
type FilterOp ¶
type FilterOp uint8
FilterOp is a comparison a Filter applies between a record's cell and a reference Value.
const ( // OpEq keeps rows whose cell equals the reference value. OpEq FilterOp = iota // OpNe keeps rows whose cell differs from the reference value. OpNe // OpLt / OpLe / OpGt / OpGe keep rows ordered below / at-or-below / above / // at-or-above the reference value (Value.compare ordering). OpLt OpLe OpGt OpGe // OpContains keeps rows whose string cell contains the reference substring. // It only matches string cells (a non-string cell never contains). OpContains )
type Group ¶
Group is one bucket of a grouped View: the shared key value, the group's rows (in the query's sort order), and its per-group aggregates.
type Kind ¶
type Kind uint8
Kind is the scalar type of a Value. The set is deliberately small — the four types a data grid needs — so a Value stays comparable and round-trips through the wire codec without loss.
type MemoryProxy ¶
type MemoryProxy struct {
// contains filtered or unexported fields
}
MemoryProxy is an in-process Proxy over a slice of validated records. It is the reference backend: its Query runs the shared Apply engine directly, and a grpcproxy Server wraps one to serve the same results across the wire.
It is safe for concurrent use.
func NewMemoryProxy ¶
func NewMemoryProxy(schema Schema, keyField string, seed ...Record) (*MemoryProxy, error)
NewMemoryProxy builds a MemoryProxy for the given schema, using keyField as the record identity (it must be a declared field). Each seed record is validated and must have a unique key. It returns an error on an unknown key field, an invalid seed, or a duplicate key.
func (*MemoryProxy) List ¶
func (m *MemoryProxy) List(_ context.Context) ([]Record, error)
List returns a deep copy of every record.
type Mutation ¶
type Mutation struct {
Kind MutationKind
Record Record
Key Value
}
Mutation is a single write. Insert and Update carry the full Record; Delete carries only the Key (the value of the backend's key field to remove).
type MutationKind ¶
type MutationKind uint8
MutationKind is the operation a Mutation performs.
const ( // MutInsert adds Record (which must be new under the key field). MutInsert MutationKind = iota // MutUpdate replaces the record whose key field equals Record's key field. MutUpdate // MutDelete removes the record whose key field equals Key. MutDelete )
type Proxy ¶
type Proxy interface {
// List returns every record, unfiltered and unordered (a fresh copy each
// call, so the caller can retain it safely).
List(ctx context.Context) ([]Record, error)
// Query applies q to the backend's records and returns the resulting View.
Query(ctx context.Context, q Query) (View, error)
// Mutate inserts, updates or deletes one record and reports any validation
// or not-found error.
Mutate(ctx context.Context, m Mutation) error
}
Proxy is the pluggable data backend a Store talks to. The same three operations are served in-process by MemoryProxy and remotely by grpcproxy.Client, so a Store — and every sort/filter/group/page/aggregate it drives — is oblivious to whether the data is local or across a websocket.
type Query ¶
type Query struct {
// Filters are ANDed: a row must satisfy all of them.
Filters []Filter
// Sorts order the surviving rows (primary key first).
Sorts []Sort
// GroupBy, when non-empty, groups the ordered rows by that field's value.
GroupBy string
// Offset skips this many rows (ungrouped) or groups (grouped) before the page.
Offset int
// Limit caps the page to this many rows (ungrouped) or groups (grouped); 0
// means no limit.
Limit int
// Aggs are computed over the whole filtered set (View.Aggregates) and, when
// grouped, over each group (Group.Aggregates).
Aggs []Agg
}
Query is a full read specification the engine applies to a set of records: keep the rows matching every Filter, order them by Sorts, optionally group by a field, take a page (Offset/Limit), and compute Aggs. Zero-value fields are inert — an empty Query returns every row unchanged with no grouping or paging.
type Record ¶
Record is one row: a set of named typed cells. It is a plain map so callers build and read rows with ordinary Go, while the Schema gives it type and the canonical encoder gives it a stable serialization (fields are always emitted in sorted-name order, so map iteration order never leaks into a comparison).
type Rule ¶
Rule validates one cell value, returning a non-nil error (whose text is the message shown to the user) when the value fails. It mirrors the string-rule shape of go-widgets/toolkit's validation, lifted to a typed Value so a schema can validate numbers and booleans, not only text.
func Required ¶
Required rejects a zero value for the field's kind: an empty string, a zero number, or false. Use it to demand a present, non-default cell.
type Schema ¶
type Schema struct {
Fields []Field
}
Schema is an ordered list of Fields — the typed shape a Record must satisfy.
func (Schema) Validate ¶
Validate checks a Record against the schema: every declared field must be present with the declared Kind and must pass its Rules, and the record must carry no field the schema does not declare. It returns the first violation, so the message is the one to surface. A nil error means the row is well-formed.
type Sort ¶
Sort is one ordering key: order by Field ascending, or descending when Desc. A Query's Sorts apply in order, the first being the primary key.
type Store ¶
type Store[R any] struct { // contains filtered or unexported fields }
Store is the typed, bindable collection at the top of the spine. It holds a Query, runs it against a Proxy (local or remote — Store neither knows nor cares), and mirrors the resulting page of rows into an mvvm.ObservableList[R] so a view re-renders itself when the data changes. Sort/filter/group/page/ aggregate all live in the Query; Load re-materialises the list from the proxy.
func NewStore ¶
NewStore builds a Store over proxy using codec to (de)serialise rows. Its Query starts empty (every row, no grouping/paging); set one with SetQuery.
func (*Store[R]) Add ¶
Add inserts a typed row, then reloads so Items reflects the new data through the current query.
func (*Store[R]) Items ¶
func (s *Store[R]) Items() *mvvm.ObservableList[R]
Items is the observable list a view binds to; it holds the decoded rows of the last Load (the flattened group rows when the query groups).
func (*Store[R]) Load ¶
Load runs the current query against the proxy, resets Items to the decoded page rows (group rows in order when grouped), and returns the full View so a caller can also read Total, Groups and Aggregates. On a proxy error Items is left unchanged.
type Value ¶
Value is one typed scalar cell. It is a tagged union kept comparable (no slices or maps) so it can be a map key, sorted, and compared by ==. Only the field selected by Kind is meaningful; the others hold their zero value.
type View ¶
View is the result of applying a Query. When the query is ungrouped, Rows is the requested page and Groups is nil; when it is grouped, Groups is the page of groups and Rows is nil. Total is the number of rows that matched the filter (before paging), and Aggregates holds the query's aggregates over that whole filtered set.
func Apply ¶
Apply runs a Query over records and returns the resulting View. It is a pure function of its inputs — no clock, no map-order dependence, no mutation of records — which is exactly why a MemoryProxy and a remote grpcproxy can share it and produce byte-identical Views: the client and the server call the same Apply on the same rows with the same Query.
The pipeline is: filter, then compute the grand aggregates over the filtered set, then sort, then either group (and paginate the groups) or paginate the rows. Sorting before grouping keeps each group's rows in the query's order and makes the group order itself deterministic.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package grpcproxy carries the data spine over gRPC: a Server that serves any data.Proxy, and a Client (itself a data.Proxy) that reaches it.
|
Package grpcproxy carries the data spine over gRPC: a Server that serves any data.Proxy, and a Client (itself a data.Proxy) that reaches it. |