ntable

package
v1.2.11 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package ntable is a general, Redis-backed table built from one primitive: the ordered set. A table is a series of ordered sets, one per cell, and the value a cell prints is by default that set's cardinality, |s|. It knows nothing about sprints: the nova-table tool is its face.

A table has three kinds of cell:

  • HEADER cells: the top row is the column labels (Column.Label, default the column name); the left column is the row labels (Row.Label, default the row key), in front of every declared column. Labels, not sets.
  • BODY cells: every body cell is an ordered set, a Redis ZSET, rendered by its column's projection: count (the cardinality, |s|), members (the members in score order, comma-joined), first, last (the lowest and highest scored member), text (a value per row, set by row set, blank when none; no set).
  • FOOTER cells: one per column, the column's fold over the body: sum or max of the counts, union of the members, none (blank). The footer row carries the table's footer label (default total). A table whose columns all fold none prints no footer row.

A cell's set is either OWNED by the table, at table:<t>:cell:<row>:<col>, and written by the cell verbs, or BOUND to a set another tool owns (the stream block's ws:<s>:<state>): a bound cell is a view, read freely and never written here (ErrBound). A row may name one member its counts and members leave out (Row.Exclude: the stream's sentinel is the stream's stop, not work).

Keys, all under one prefix so an ACL row can grant them (~table:*), plus the registry set tables:

tables                     SET  every table name (List)
table:<t>                  HASH order (the column names, comma-joined),
                                footer (the footer label), created_at,
                                col:<name> = <projection>:<fold>:<width>:<label>
table:<t>:rows             ZSET row key -> rank (the render order)
table:<t>:row:<r>          HASH label, exclude, owner, key:<col> (a bound
                                cell's set; absent: the owned cell)
table:<t>:cell:<r>:<c>     ZSET an owned cell

Each table operation is one Redis function call. Read-only snapshots include the definition, current rows, bindings and cells even on a cold read. QueueCells remains available to callers already holding a shape, so they can include cell reads in their own larger pipeline. Writes validate bindings and permissions before changing any key.

Index

Constants

View Source
const (
	LimitManifestBytes   = 1 << 20 // canonical encoded request
	LimitChangedEntries  = 128     // entries with changes
	LimitGuardEntries    = 1024    // guard-only entries
	LimitMemberIDBytes   = 256
	LimitFieldValueBytes = 64 << 10
	LimitSetFields       = 128  // set fields per member
	LimitUnsetFields     = 1000 // unset fields per member
	LimitFieldGuards     = 1000 // guards per member
	LimitOneOfOptions    = 1000 // options in one guard
	LimitReadSetMembers  = 1024
	LimitColumns         = 1000               // columns per table
	LimitRows            = 100000             // rows per table
	LimitManifestProps   = 64                 // properties a manifest sets, expects or expects absent (each)
	LimitTableProps      = 64                 // properties a table holds at an epoch
	LimitReceiptBytes    = LimitManifestBytes // the encoded batch delta of one receipt
	LimitBatchValueBytes = 16 << 20           // bytes of the field values a batch's entries name, before and after

)

The bounds of a batch manifest and of a read set. table.lua holds the same numbers (T.limits) and docs/SPEC-NOVA-TABLE.md states them; a test compares the three. A refusal names the bound and the count found, never the input.

View Source
const (
	Count   = "count"
	Members = "members"
	First   = "first"
	Last    = "last"
	Text    = "text"
)

The projections: what a body cell prints.

View Source
const (
	Sum    = "sum"
	Max    = "max"
	Union  = "union"
	Avg    = "avg"    // the mean of a count column over the rows
	Pooled = "pooled" // a pct column's footer: the named counts summed over every row's counts summed (never the mean of percentages)
	None   = "none"
)

The folds: what a footer cell prints over its column.

View Source
const (
	FnClear          = "ns_table_clear"
	FnCreate         = "ns_table_create"
	FnSet            = "ns_table_set"
	FnRowSet         = "ns_table_row_set"
	FnRowsAdd        = "ns_table_rows_add"
	FnRowsHide       = "ns_table_rows_hide"
	FnDrop           = "ns_table_drop"
	FnDropDefinition = "ns_table_drop_definition"
	FnMemberCreate   = "ns_table_member_create"
	FnMemberFind     = "ns_table_member_find"
	FnRowAdd         = "ns_table_row_add"
	FnRowDel         = "ns_table_row_del"
	FnCellAdd        = "ns_table_cell_add"
	FnCellRemove     = "ns_table_cell_remove"
	FnCellMove       = "ns_table_cell_move"
	FnBind           = "ns_table_bind"
	FnRead           = "ns_table_read"
	FnCheck          = "ns_table_check"
	FnList           = "ns_table_list"
	FnMembers        = "ns_table_members"
	FnApply          = "ns_table_apply"
	FnReadSet        = "ns_table_read_set"
)
View Source
const CheckedBeforeSending = "checked before sending, so this call changed nothing; it says nothing about an earlier call with the same operation id"

CheckedBeforeSending is what a refusal made before anything is sent says of itself; the library and the command both say it. The store looks an operation up before it judges the request, so a manifest that the current rules refuse can still have been applied earlier, under looser rules: this refusal is about this call only.

View Source
const DefaultFooter = ""

DefaultFooter is the footer label a table has when none is set: none; the footer row still prints the folds, with a blank label cell, and `create --footer <label>` names it.

View Source
const FnMove = "ns_oset_move"

FnMove is the ordered-set move library function: ZREM from, ZADD to, in one call, refused NOTMEMBER when the member is not in from (pkg/nsprint/fn/lua/table.lua).

View Source
const MaxViewState = 64

MaxViewState bounds a view's state text, in bytes.

View Source
const ReceiptValueBytes = 64

ReceiptValueBytes is the longest field value a receipt, the change event and the operation record's result hold in full; a longer one is recorded as its length and SHA-1 (FieldChange.BeforeBytes, BeforeSHA1). The record's request is the manifest as sent, in full, because replay compares bytes. table.lua holds the same number (T.receipt_value_bytes); a test compares them.

View Source
const Registry = "tables"

Registry is the SET of every table name.

Variables

View Source
var (
	ErrExists            = errors.New("exists with another definition")
	ErrOccupied          = errors.New("shape would delete or hide placed members")
	ErrOwnedAlias        = errors.New("binding target is table-owned storage")
	ErrStale             = errors.New("requested epoch is stale, not the active epoch")
	ErrEpochAhead        = errors.New("requested epoch is ahead of the active epoch")
	ErrMemberEpoch       = errors.New("member belongs to another epoch")
	ErrPlaced            = errors.New("member already has a place in this table")
	ErrDrift             = errors.New("member record and owned set disagree")
	ErrMemberExists      = errors.New("member identity already exists")
	ErrRevisionMismatch  = errors.New("table revision mismatch")
	ErrMemberRevision    = errors.New("member revision mismatch")
	ErrFieldGuard        = errors.New("failed field guard")
	ErrPlaceGuard        = errors.New("failed place guard")
	ErrPropGuard         = errors.New("failed property guard")
	ErrOpConflict        = errors.New("operation ID conflict")
	ErrLimit             = errors.New("limit exceeded")
	ErrReservedField     = errors.New("reserved field write")
	ErrDuplicateMember   = errors.New("duplicate manifest member")
	ErrInvalidScore      = errors.New("invalid score")
	ErrCounterOverflow   = errors.New("counter overflow")
	ErrMutation          = errors.New("incompatible mutation")
	ErrWrongType         = errors.New("WRONGTYPE")
	ErrMalformedManifest = errors.New("malformed manifest")
	// ErrOrphan is a table that is gone while its identity record is left: a
	// verb on it refuses, and drop --definition removes the record.
	ErrOrphan = errors.New("orphan table identity")
	// ErrResidue is a create refused because rows of an earlier table of the
	// name are left at the epoch it would open at.
	ErrResidue = errors.New("rows of an earlier table are left")
	// ErrUnknownOutcome is a transport failure: the store did not answer, so the
	// batch may or may not have been applied (changed=unknown). Send the same
	// manifest again with the same operation id.
	ErrUnknownOutcome = errors.New("the store did not confirm the batch")
)
View Source
var ErrNoTable = errors.New("no such table")
View Source
var ErrNoView = errors.New("no such view")

ErrNoView is a view that is not there.

View Source
var ErrNotMember = errors.New("NOTMEMBER")

ErrNotMember is the refusal of a move whose set does not hold the member it would leave.

Functions

func Bind

func Bind(ctx context.Context, c redis.Cmdable, t Table, now time.Time, opts ...WriteOptions) error

Bind replaces the caller-owned table shape in one atomic call. Existing owned cells of retained rows survive; removed rows leave bound sets alone. Omitting a row holding owned members or nonempty text is refused until the caller explicitly clears that content (or deletes the row).

func CellAdd

func CellAdd(ctx context.Context, c redis.Cmdable, name, row, col, member string, score float64, opts ...WriteOptions) (int64, error)

Single-member helpers use the same list operation and wire protocol.

func CellKey

func CellKey(table, row, col string) string

func CellKeyAt

func CellKeyAt(table, row, col string, epoch uint64) string

func CellMove

func CellMove(ctx context.Context, c redis.Cmdable, name, row, from, to, member string, opts ...WriteOptions) (int64, error)

func CellRemove

func CellRemove(ctx context.Context, c redis.Cmdable, name, row, col, member string, opts ...WriteOptions) (int64, error)

func CellText

func CellText(cols []Column, r Row, j int) string

CellText returns a full, unpadded projected cell. Render and machine-readable show use the same value, including text, percentages and unread dependencies.

func CellsAdd

func CellsAdd(ctx context.Context, c redis.Cmdable, name, row, col string, score float64, members []string, opts ...WriteOptions) (int64, error)

CellsAdd atomically adds every member at one score and returns the cell count.

func CellsMove

func CellsMove(ctx context.Context, c redis.Cmdable, name, row, from, to string, members []string, opts ...WriteOptions) (int64, error)

CellsMove keeps scores and refuses the complete list if any member is absent or inconsistent.

func CellsRemove

func CellsRemove(ctx context.Context, c redis.Cmdable, name, row, col string, members []string, opts ...WriteOptions) (int64, error)

CellsRemove removes a list; absent members are accepted no-ops.

func ChangesKey

func ChangesKey(table string) string

func CheckBatchBounds

func CheckBatchBounds(m *BatchManifest) error

CheckBatchBounds applies every bound to a decoded manifest, in the order the server applies them: per entry (id, set, unset, guards, one_of), then the entry counts.

func Clear

func Clear(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int64, error)

func Create

func Create(ctx context.Context, c redis.Cmdable, t Table, now time.Time, opts ...WriteOptions) error

Create checks the definition and writes it atomically in one round trip.

func DefKey

func DefKey(table string) string

DefKey is the table's definition hash.

func Drop

func Drop(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int, error)

Drop removes the active epoch and its owned cells. The template and older epochs survive; a later epoch starts with the same definition and no rows.

func DropDefinition

func DropDefinition(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int, error)

DropDefinition also removes the stable template. Materialised epoch snapshots and immutable member identities remain available for inspection.

func EpochPrefix

func EpochPrefix(table string, epoch uint64) string

EpochPrefix is the storage namespace for one table generation.

func FormulaArg

func FormulaArg(projection string) string

FormulaArg is the text between a formula's parentheses.

func IdentityKey

func IdentityKey(table string) string

IdentityKey is the table's identity hash: its epoch_key, epoch_field and member_prefix, written with the table and removed by drop --definition.

func IsFormula

func IsFormula(projection string) bool

IsFormula is whether a projection is computed rather than read.

func IsPct

func IsPct(projection string) bool

IsPct is whether a projection is a percentage, pct(...).

func IsRefusal

func IsRefusal(err error) bool

IsRefusal says err is a refusal: the store or a rule said no and nothing changed.

func IsSum

func IsSum(projection string) bool

IsSum is whether a projection is a sum of count columns, sum(...).

func List

func List(ctx context.Context, c redis.Cmdable) ([]string, error)

func MemberCreate

func MemberCreate(ctx context.Context, c redis.Cmdable, name, id string, opts ...WriteOptions) error

MemberCreate allocates an unplaced identity in the table's record namespace. Existing IDs, including removed members and older epochs, are refused.

func MemberKey

func MemberKey(id string) string

MemberKey is reserved metadata, outside every valid table-name prefix.

func ParseWidths

func ParseWidths(spec string) (map[string]int, error)

ParseWidths reads col=n,col=n.

func PropsKeyAt

func PropsKeyAt(table string, epoch uint64) string

PropsKeyAt is the table's properties at the epoch (L1 contract amendment, table properties): a hash of name -> value beside the rows key.

func QueueViewState

func QueueViewState(ctx context.Context, p redis.Pipeliner, name, text string) *redis.Cmd

QueueViewState queues ViewState on a pipeline or a transaction, so a caller writes a view's state in the same MULTI/EXEC as a record of its own (the state it shows); ViewStateResult reads the queued call's answer after Exec.

func Render

func Render(t Table, opts RenderOpts) string

Render prints the table as fixed-width text: the header row of column labels, a rule, one line per row (its label, then its cells), and, when any column folds, a rule and the footer row. Text, members, first and last cells are left-aligned; count cells right-aligned; a column's cells are separated by " | " and the rule joins dashes with "-+-". A cell whose set did not come back prints "?", and so does a fold over it. The last column is padded only when it is right-aligned, so no line ends in a blank. A table always renders, with its header and its footer, and with no body line when it has no row (the owner 2026-09-30, the owner's ruling: tables and rows always show, empty or not); with no body line the footer sits under the header's one rule, and the rule above the footer is not drawn (the owner, 2026-10-02: "when the work stream table is empty, please just show the summary row" / "not the extra --------------------+-----------+----------- etc.").

The row's label is always the first column (the owner 2026-09-27, the live session: eight benches rendered as eight anonymous rows of numbers), put in front of every declared column, and the footer label prints under it instead of eating the first count column's fold. Its header is the table's name when the caller gives one (opts.Title), else "row". A text column is a column like any other: it prints the row's value (row set), blank when the row has none; it never stands in for the label (a reviewer's read of #4456: a value in a leading text column made the row's key vanish; "the row identity must still have its own visible cell"). Display values are escaped before measuring widths: stored text cannot add terminal controls or line breaks, and rendering never changes the snapshot.

func RenderTables

func RenderTables(title string, tables []Table, opts RenderOpts) string

RenderTables is the title line, then every table's render, one blank line between two; a table kept and read but not drawn (HiddenTable) prints nothing and leaves no gap. An empty table prints its header and footer.

func RevisionKey

func RevisionKey(table string) string

func RowDel

func RowDel(ctx context.Context, c redis.Cmdable, name, key string, opts ...WriteOptions) (bool, error)

func RowKey

func RowKey(table, row string) string

func RowKeyAt

func RowKeyAt(table, row string, epoch uint64) string

func RowSet

func RowSet(ctx context.Context, c redis.Cmdable, name, key string, texts map[string]string, opts ...WriteOptions) (int, error)

RowSet writes text values as one validated mutation, with a single receipt.

func RowSetMany

func RowSetMany(ctx context.Context, c redis.Cmdable, name string, rows map[string]map[string]string, opts ...WriteOptions) error

RowSetMany sets the text cells of several rows of one table in one round trip: one ns_table_row_set per row, all in one pipeline, each its own write (its own revision and change event), as RowSet writes it. An error names the first row the store refused, or the exchange that failed.

func RowsAdd

func RowsAdd(ctx context.Context, c redis.Cmdable, name string, keys []string, opts ...WriteOptions) (int, error)

RowsAdd adds a list of rows using the same staged writer as RowAdd.

func RowsAddWithSpec

func RowsAddWithSpec(ctx context.Context, c redis.Cmdable, name string, keys []string, spec RowSpec, opts ...WriteOptions) (int, error)

RowsAddWithSpec applies one metadata specification to every named row.

func RowsHide

func RowsHide(ctx context.Context, c redis.Cmdable, name string, hide bool, keys []string, opts ...WriteOptions) (int, error)

RowsHide hides rows from the render while retaining their contribution to folds.

func RowsKey

func RowsKey(table string) string

The original key helpers name epoch zero for existing callers.

func RowsKeyAt

func RowsKeyAt(table string, epoch uint64) string

func SameDefinition

func SameDefinition(a, b Table) bool

SameDefinition says two tables declare the same columns and footer.

func SameShape

func SameShape(a, b Table) bool

SameShape says two tables have the same definition and the same rows with the same bindings, labels, excludes and owners (the cells' values aside): what a writer compares before binding again.

func Set

func Set(ctx context.Context, c redis.Cmdable, name string, change SetOpts, opts ...WriteOptions) (int, error)

Set validates and commits a complete definition edit in one call. The return value counts physical keys moved when renaming, otherwise zero.

func SummaryLine

func SummaryLine(v View, first Table) string

SummaryLine is a stored view's summary line over its first table's snapshot, "" for no line: the view's state alone while it has one ("STOPPED", nothing more: no counts, no percent, no ETA), else "x/y z% -> ETA" when the view names a done column. The counts pool the first table's count columns; unread input, hidden rows and columns included, leaves them unknown. ETA has no value until change-stream rate sampling is available.

func ValidName

func ValidName(s string) bool

ValidName says a table or column name is one word of letters, digits, '_', '.' and '-', starting with a letter, digit or '_'.

func ValidRowKey

func ValidRowKey(s string) bool

ValidRowKey says a row key is non-empty, valid UTF-8 and holds no ASCII control character.

func ValidViewState

func ValidViewState(text string) bool

ValidViewState says a state text is one a view takes: empty (none), or one line of at most MaxViewState bytes with no control characters.

func ValidateColumns

func ValidateColumns(cols []Column) error

ValidateColumns refuses a column list a table cannot carry: a bad name, a repeated name, an unknown projection or fold, or a fold that does not fit its projection (sum and max fold counts; union folds members, first and last; none folds anything; a text column folds none).

func ViewDelete

func ViewDelete(ctx context.Context, c redis.Cmdable, name string) (int64, error)

ViewDelete removes only presentation configuration; tables are untouched.

func ViewList

func ViewList(ctx context.Context, c redis.Cmdable) ([]string, error)

ViewList returns stored view names in lexical order, in one exchange.

func ViewSet

func ViewSet(ctx context.Context, c redis.Cmdable, v View) error

ViewSet writes a view; every table must exist.

func ViewState

func ViewState(ctx context.Context, c redis.Cmdable, name, text string) error

ViewState sets a view's state text, the summary line shown alone in place of the counts while it is set; "" clears it and the counts show again. The view must exist.

func ViewStateResult

func ViewStateResult(name string, cmd *redis.Cmd) error

ViewStateResult is the answer of a call QueueViewState queued, after Exec: nil, a refusal (errors.Is ErrNoView when the view is not there), or the store's error.

Types

type BatchDelta

type BatchDelta struct {
	OperationID   string             `json:"operation_id"`
	Digest        string             `json:"digest"`
	Actor         string             `json:"actor"`
	SelectedCount int                `json:"selected_count"`
	GuardCount    int                `json:"guard_count"`
	ChangedCount  int                `json:"changed_count"`
	Members       []BatchMemberDelta `json:"members"`
	// Props are the table's properties the batch changed, name -> new value
	// (L1 contract amendment, table properties).
	Props map[string]string `json:"props,omitempty"`
}

BatchDelta records the applied batch outcome for change stream and receipts.

func (*BatchDelta) UnmarshalJSON

func (b *BatchDelta) UnmarshalJSON(data []byte) error

type BatchManifest

type BatchManifest struct {
	Schema                int                `json:"schema"`
	Table                 string             `json:"table"`
	Epoch                 string             `json:"epoch"`
	ExpectedTableRevision string             `json:"expected_table_revision"`
	OperationID           string             `json:"operation_id"`
	Actor                 string             `json:"actor,omitempty"`
	Members               []BatchMemberEntry `json:"members"`
	// Props are the table's properties the batch sets, PropExpect the ones it
	// expects present with a value and PropAbsent the ones it expects absent,
	// checked before any write and applied in the same atomic call as the
	// members (L1 contract amendment, table properties, section 4).
	Props      map[string]string `json:"props,omitempty"`
	PropExpect map[string]string `json:"prop_expect,omitempty"`
	PropAbsent []string          `json:"prop_absent,omitempty"`
}

BatchManifest specifies an atomic set of preconditions and mutations across members in a table.

func ValidateBatchManifestRaw

func ValidateBatchManifestRaw(raw []byte) (*BatchManifest, error)

ValidateBatchManifestRaw reads raw manifest bytes as a batch manifest and applies every rule the server applies that needs no store: the manifest's size, UTF-8, exact-case keys, no key twice, each value's type, the bounds, and the combinations of changes. Nothing is coerced. The errors are of two kinds: a *ManifestError, a manifest that cannot be read as one (it names the place, members[0].create.score, in the manifest's words), and a *RuleError or a *LimitError, a manifest that reads as one and that a rule refuses; each is what the store would answer, with the same code.

type BatchMemberDelta

type BatchMemberDelta struct {
	ID          string `json:"id"`
	BeforePlace string `json:"before_place"`
	AfterPlace  string `json:"after_place"`
	// BeforeScore and AfterScore are the scores parsed; BeforeScoreText and
	// AfterScoreText are the exact decimal strings the store holds, which two
	// different scores never share. Nil is no score (unplaced).
	BeforeScore     *float64               `json:"-"`
	AfterScore      *float64               `json:"-"`
	BeforeScoreText *string                `json:"before_score"`
	AfterScoreText  *string                `json:"after_score"`
	BeforeRev       string                 `json:"before_rev"`
	AfterRev        string                 `json:"after_rev"`
	FieldsSet       map[string]string      `json:"fields_set"`
	FieldsUnset     []string               `json:"fields_unset"`
	Fields          map[string]FieldChange `json:"fields"`
}

BatchMemberDelta records before and after state for a member affected by a batch.

func (*BatchMemberDelta) UnmarshalJSON

func (b *BatchMemberDelta) UnmarshalJSON(data []byte) error

type BatchMemberEntry

type BatchMemberEntry struct {
	ID     string            `json:"id"`
	Expect *MemberExpect     `json:"expect,omitempty"`
	Create *MemberCreateOp   `json:"create,omitempty"`
	Move   *MemberMoveOp     `json:"move,omitempty"`
	Remove bool              `json:"remove,omitempty"`
	Set    map[string]string `json:"set,omitempty"`
	Unset  []string          `json:"unset,omitempty"`
}

BatchMemberEntry defines expectations and mutations for one member.

type BoundError

type BoundError struct{ Table, Row, Col, Key, Owner string }

BoundError names the other writer. The table may read the binding but cannot acquire write ownership merely by displaying it.

func (*BoundError) Error

func (e *BoundError) Error() string

type Cell

type Cell struct {
	Key     string
	Bound   bool
	Count   int64
	Members []Member
	Unread  bool
	// UnreadWhy says why the set did not come back: the key and the type found
	// ("key K is string, expected zset"), or the store's error.
	UnreadWhy string
}

Cell is one body cell: its set's key ("" for a text column), whether the set is bound (owned elsewhere), and what the last read found: the count, the members (for members, first and last), and Unread when the set did not come back (it prints "?", never a false 0).

type CellSelection

type CellSelection struct {
	Row string `json:"row"`
	Col string `json:"col"`
}

CellSelection specifies a row and column for ReadSet.

type CellsCmd

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

CellsCmd is one queued read of every body cell of a table the caller holds in memory: the ZCARD (and the excluded member's ZSCORE) of every count cell, the ZRANGE of every members, first and last cell. Result fills the table's cells in place.

func QueueCells

func QueueCells(ctx context.Context, pipe redis.Pipeliner, t *Table) *CellsCmd

QueueCells queues the cells of t on pipe: one command per count cell (two with a row exclude), one per members, first or last cell, none for a text column. It is how a caller that already knows a table's shape (the sprint tick, whose stream rows are ws:order) reads every cell in its own one pipeline.

func (*CellsCmd) Result

func (q *CellsCmd) Result()

Result fills every queued cell: Count and Members from the store, Unread for a cell whose read did not come back (it prints "?", never a false 0).

type CheckReport

type CheckReport = typedrec.TableCheck

CheckReport covers both directions of record/set membership in one instant.

func Check

func Check(ctx context.Context, c redis.Cmdable, name string) (CheckReport, error)

Check is an explicit maintenance operation: it scans the member namespace and the epoch's owned cells atomically, including orphan records and hidden cells. Its cost scales with the namespace; it is not run on the mutation hot path.

type Column

type Column struct {
	Name       string
	Label      string
	Projection string
	Fold       string
	Width      int
}

Column is one column: its name (the key its cells and definition use), its header label, the projection its body cells print, the fold its footer cell prints, and a fixed width (0: as wide as its widest cell).

func ParseColumn

func ParseColumn(spec string) (Column, error)

ParseColumn reads one column declaration, name[:projection[:fold[:label]]] (defaults: count, sum for a count or sum(...) column, pooled for a pct column and none otherwise, the name).

func ParseColumns

func ParseColumns(specs string) ([]Column, error)

ParseColumns reads a comma-separated list of column declarations.

func (Column) HasSet

func (c Column) HasSet() bool

HasSet is whether a column's cells are ordered sets in the store (text and formula columns have none).

func (Column) LabelOrName

func (c Column) LabelOrName() string

LabelOrName is the header cell.

type CountCmd

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

CountCmd is one queued count of an ordered set: its ZCARD, less one when the excluded member is in it (its ZSCORE rides the same pipeline). It is the one count every reader of a set counts through: the sprint's card count leaves the stream's sentinel out this way.

func QueueCount

func QueueCount(ctx context.Context, pipe redis.Pipeliner, key, exclude string) *CountCmd

QueueCount queues the count of the set at key on pipe, leaving exclude out when it is a member ("" excludes nothing).

func (*CountCmd) Result

func (c *CountCmd) Result() (int64, error)

Result is the count: the ZCARD's error when it failed; the excluded member's absence (redis.Nil) is not an error.

type FieldChange

type FieldChange struct {
	Before      *string `json:"before"`
	After       *string `json:"after"`
	BeforeBytes int     `json:"before_bytes,omitempty"`
	BeforeSHA1  string  `json:"before_sha1,omitempty"`
	AfterBytes  int     `json:"after_bytes,omitempty"`
	AfterSHA1   string  `json:"after_sha1,omitempty"`
}

FieldChange records before and after values for an application field. Absence is represented by nil, distinguished from a present empty string.

A value longer than ReceiptValueBytes is not recorded: its side is nil and carries the value's length and SHA-1 (BeforeBytes, BeforeSHA1), so a nil side with no length is an absent field and a nil side with a length is a long one.

type FieldGuard

type FieldGuard struct {
	Equals *string  `json:"equals,omitempty"`
	Absent *bool    `json:"absent,omitempty"`
	OneOf  []string `json:"one_of,omitempty"`
}

FieldGuard checks a member application field's exact value, absence, or inclusion.

type Formula

type Formula struct {
	Sum  bool
	Part string
	Over []string
}

Formula is a formula projection read: for pct, Part is the numerator and Over the named denominator (nil: every count column of the row); for sum, Over is the columns added and Part is empty.

func ParseFormula

func ParseFormula(projection string) (Formula, error)

ParseFormula reads a formula projection: pct(<col>), pct(<col>/<a>+<b>+...) or sum(<a>+<b>+...). The error says what is wrong with the text; that the named columns exist and are counts is the table's check (ValidateColumns).

func (Formula) Inputs

func (f Formula) Inputs() []string

Inputs is every column the formula reads, each once, in the order written.

type LimitError

type LimitError struct {
	Name            string
	Bound, Observed int
	Member          string
	// AtLeast says the call stopped counting when it passed the bound: the true
	// size is at least Observed.
	AtLeast bool
}

LimitError is a named LIMIT refusal: the bound and the count found, and the member at fault when one is. It wraps ErrLimit.

func (*LimitError) Advice

func (e *LimitError) Advice() string

Advice says how to get under the bound: what to narrow. A request that is narrowed is a different transaction, with its own operation id.

func (*LimitError) Error

func (e *LimitError) Error() string

func (*LimitError) Unwrap

func (e *LimitError) Unwrap() error

type ManifestError

type ManifestError struct {
	Where, Msg string
}

ManifestError is a manifest that cannot be read as a manifest: it is not JSON, names a key it may not, or holds a value of the wrong type. It says where, in the manifest's own words (members[0].create.score), and never in the parser's.

func (*ManifestError) Error

func (e *ManifestError) Error() string

func (*ManifestError) Is

func (e *ManifestError) Is(target error) bool

type Member

type Member struct {
	Member string
	Score  float64
}

Member is one member of an ordered set with its score.

func CellMembers

func CellMembers(ctx context.Context, c redis.Cmdable, name, row, col string) ([]Member, error)

type MemberCreateOp

type MemberCreateOp struct {
	Row   string  `json:"row"`
	Col   string  `json:"col"`
	Score float64 `json:"score"`
}

MemberCreateOp places a new member at row, column, and score.

type MemberExpect

type MemberExpect struct {
	Absent   bool                  `json:"absent,omitempty"`
	Revision string                `json:"revision,omitempty"`
	Place    *PlaceExpect          `json:"place,omitempty"`
	Fields   map[string]FieldGuard `json:"fields,omitempty"`
}

MemberExpect guards an existing or absent member record before mutation.

type MemberLocation

type MemberLocation = typedrec.TableMember

MemberLocation is an indexed placement in this table, or missing/unplaced.

func MemberFind

func MemberFind(ctx context.Context, c redis.Cmdable, name, id string) (MemberLocation, error)

MemberFind reads and verifies one member's owned placement atomically. It does not search external bindings; Check audits the complete namespace.

type MemberMoveOp

type MemberMoveOp struct {
	Row   string   `json:"row"`
	Col   string   `json:"col"`
	Score *float64 `json:"score,omitempty"`
}

MemberMoveOp moves an existing member to row, column, with optional new score.

type Place

type Place struct {
	Where string // first, last, before, after
	Ref   string // the neighbour, for before and after
}

Place is a position in an order: first, last, or before or after Ref.

type PlaceExpect

type PlaceExpect struct {
	Row string `json:"row"`
	Col string `json:"col"`
}

PlaceExpect checks the expected row and column of a placed member.

type ReadCmd

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

func (*ReadCmd) Result

func (q *ReadCmd) Result() (Table, bool, error)

Result's changed value is retained for source compatibility and is always false: the function returns one consistent shape and its cells together.

type ReadSetCmd

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

ReadSet reads members in one round trip from one consistent snapshot of one table (ns_table_read_set, a read-only function): each member's place, score, revision and fields, the members that are missing, and the table's epoch and revision, which a manifest's expected_table_revision is prepared from.

The scope is one of two shapes, each nonempty: Members (ids) or Selection (row and column pairs, every member of each cell). An empty scope, both lists together, or more than LimitReadSetMembers members is refused. epoch is optional (at most one value): it reads a materialised epoch of the table instead of the active one, and an epoch ahead of the active one is refused (ErrEpochAhead). Errors are refusals (IsRefusal) that wrote nothing, or a transport error from the client. ReadSetCmd holds a queued ns_table_read_set command.

func QueueReadSet

func QueueReadSet(ctx context.Context, c redis.Cmdable, table string, scope ReadSetScope, epoch ...uint64) (*ReadSetCmd, error)

QueueReadSet queues ns_table_read_set on c (a redis.Pipeliner or Cmdable).

func QueueReadSetMembers

func QueueReadSetMembers(ctx context.Context, c redis.Cmdable, table string, members []string, epoch ...uint64) (*ReadSetCmd, error)

QueueReadSetMembers queues ns_table_read_set for a list of member IDs.

func (*ReadSetCmd) Result

func (q *ReadSetCmd) Result() (ReadSetResult, error)

Result decodes the result of a queued ReadSet.

type ReadSetMember

type ReadSetMember struct {
	ID       string
	Revision uint64
	Placed   bool
	Row      string
	Col      string
	Score    float64
	// ScoreText is the score exactly as the store holds it.
	ScoreText string
	Fields    map[string]string
}

ReadSetMember represents one member returned by ReadSet.

type ReadSetResult

type ReadSetResult struct {
	Table    string
	Epoch    uint64
	Revision uint64
	Members  []ReadSetMember
	Missing  []string
}

ReadSetResult contains the verified atomic snapshot of members and table revision.

func ReadSet

func ReadSet(ctx context.Context, c redis.Cmdable, table string, scope ReadSetScope, epoch ...uint64) (ReadSetResult, error)

func ReadSetMembers

func ReadSetMembers(ctx context.Context, c redis.Cmdable, table string, members []string, epoch ...uint64) (ReadSetResult, error)

ReadSetMembers is ReadSet for member ids: one round trip, one snapshot. The optional epoch is the epoch to read, as in ReadSet.

func (ReadSetResult) IsMissing

func (r ReadSetResult) IsMissing(id string) bool

IsMissing says the id was asked for and does not exist as a member of the table. A missing member is an answer, not an error: it is listed in Missing.

func (ReadSetResult) Member

func (r ReadSetResult) Member(id string) (ReadSetMember, bool)

Member returns the member with the id from the members that were found; ok is false for an id that is missing (see IsMissing) or was not asked for.

type ReadSetScope

type ReadSetScope struct {
	Members   []string        `json:"members,omitempty"`
	Selection []CellSelection `json:"selection,omitempty"`
}

ReadSetScope specifies members or row/col selections to read atomically.

type Reader

type Reader struct{ Name string }

Reader queues a read-only server snapshot, even on the first tick or a changed shape. QueueCells remains available for callers holding a shape.

func NewReader

func NewReader(name string) *Reader

func (*Reader) Queue

func (r *Reader) Queue(ctx context.Context, pipe redis.Pipeliner) *ReadCmd

func (*Reader) Read

func (r *Reader) Read(ctx context.Context, c redis.Cmdable) (Table, error)

type Receipt

type Receipt struct {
	ID                   string
	Epoch, Before, After uint64
	Outcome              string
	BatchDelta           *BatchDelta
	// Replay is true when ApplyBatch returned the receipt recorded for an
	// operation it had already applied, and wrote nothing.
	Replay bool
}

Receipt identifies the durable table change. Idem is recorded as caller metadata; this primitive does not deduplicate attempts.

func ApplyBatch

func ApplyBatch(ctx context.Context, c redis.Cmdable, manifest BatchManifest) (Receipt, error)

ApplyBatch commits an atomic batch of member mutations and preconditions against one table in one round trip (ns_table_apply): every guard is read against one pre-state, and either every change is written with one receipt or nothing is.

It runs the manifest through the Go validator (ValidateBatchManifestRaw) before it sends anything, so a manifest that a rule refuses never reaches the store. Its errors are of three kinds, told apart with errors.Is and errors.As:

  • a refusal (IsRefusal): the store or a rule said no, nothing changed, and the error says the code, the operation, the member, what was expected against what was found, changed=no and a next command. It wraps the sentinel of its code (ErrNotMember, ErrMemberRevision, ErrLimit, ErrStale, ErrEpochAhead, ErrOpConflict, ...).
  • a manifest that cannot be read as one (ErrMalformedManifest, a *ManifestError): not JSON, an unknown key, a value of the wrong type. It names the place in the manifest.
  • a transport failure (ErrUnknownOutcome): the store did not answer, so the batch may or may not have been applied. Send the same manifest again with the same operation id: it returns the original receipt if the batch was applied and applies it if it was not.

Operation identity is the table, the epoch and the operation id. Sending the same request again returns the receipt recorded for it (Receipt.Replay is true) and writes nothing, even after the table has moved on; a different request under the same id is refused (ErrOpConflict). manifest.Epoch is the epoch the caller observed: an epoch behind the active one is ErrStale, one ahead of it ErrEpochAhead. Operation records do not expire, but a drop of the table ends them.

func ApplyBatches

func ApplyBatches(ctx context.Context, c redis.Cmdable, manifests []BatchManifest) ([]Receipt, []error)

ApplyBatches applies manifests of one table in one round trip, in one MULTI/EXEC: each is its own batch, applied or refused as ApplyBatch applies it, and none runs between them, so a refused one leaves every one after it refused on the table revision it expected. The receipts and errors are each manifest's; a manifest refused before sending (a rule or a bound this process checks) is its error, and none after it is sent.

type Refusal

type Refusal struct {
	Code     string // the store's refusal code: NOTMEMBER, LIMIT, MEMBERREVISION, ...
	Location string // the operation: table "demo" batch "op-1" member "a"
	Sentence string // what was expected against what was found
	Next     string // a command that runs when pasted
	Guarded  bool   // a batch or a read set: it wrote nothing, and says so
	// contains filtered or unexported fields
}

Refusal is the store's no, or a rule's, with its code and its sentence: the operation, what was expected against what was found, whether anything changed, and the next command. A refusal is not a transport failure (ErrUnknownOutcome) and not a manifest that cannot be read (ErrMalformedManifest); a caller tells them apart with errors.Is, or IsRefusal.

func (*Refusal) Error

func (r *Refusal) Error() string

func (*Refusal) Unwrap

func (r *Refusal) Unwrap() error

type RenderOpts

type RenderOpts struct {
	// Widths fixes a column's width by name (a minimum: a longer cell is
	// not cut), over the column's own Width; 0 leaves it as wide as its
	// widest cell.
	Widths map[string]int
	// LabelWidth fixes the row-label column's width the same way (the
	// sprint's stream block keeps its names 25 wide); 0 fits the labels.
	LabelWidth int
	// Title is the table's name, printed in the top-left cell, the header
	// of the row-label column (the owner 2026-09-27: "tables need a title";
	// "the title goes where 'row' is currently").
	Title string
}

RenderOpts is what Render is told beyond the table.

type Reorder

type Reorder struct {
	Item string
	Place
}

Reorder puts one column or one row at a place; nothing else moves.

type Row

type Row struct {
	Key     string
	Label   string
	Exclude string
	Owner   string
	Cells   []Cell
	Texts   map[string]string // a text column's value for this row (row set <col>=<value>), else blank
	Hidden  bool              // kept and counted in the folds, not drawn (row hide)
}

Row is one row: its key, its label (the row header; "" prints the key), the member its counts leave out ("" for none), the verb that owns its bound sets (for the refusal that names it), and one cell per column.

func NewRow

func NewRow(t Table, key string) Row

NewRow is a row of t with every non-text cell owned, before any binding.

func RowAdd

func RowAdd(ctx context.Context, c redis.Cmdable, name, key string, spec RowSpec, opts ...WriteOptions) (Row, error)

func (Row) LabelOrKey

func (r Row) LabelOrKey() string

LabelOrKey is the row header cell.

type RowSpec

type RowSpec struct {
	Label   string            `json:"label"`
	Exclude string            `json:"exclude"`
	Owner   string            `json:"owner"`
	Binds   map[string]string `json:"binds,omitempty"`
}

type RuleError

type RuleError struct {
	Code   string // the store's refusal code for the same rule
	Member string // the member at fault, if one is
	Msg    string // the sentence, naming the member
	Detail string // the sentence without the member
	// contains filtered or unexported fields
}

RuleError is a manifest that reads as a manifest and that a rule refuses: a repeated member, a set and an unset of one field, a reserved field, a create with a move, an empty members array. It is a refusal, as the store's are, and says what the store would: a code and a sentence.

func (*RuleError) Error

func (e *RuleError) Error() string

func (*RuleError) Unwrap

func (e *RuleError) Unwrap() error

type SetOpts

type SetOpts struct {
	Footer     *string
	Rename     string
	Columns    []Column
	Hidden     *[]string
	Hide, Show []string // atomic deltas to the hidden column list
	Visible    *bool
	// One column added (at the end, or where At says), removed (refused
	// while it holds members or text) or moved; the other columns stand.
	ColAdd  *Column
	ColAt   *Place // where ColAdd enters; nil is last
	ColDel  string
	ColMove *Reorder
	// The rows' order: all sorted, then the named rows first, then one row
	// moved, in that order when several are given. A standing sort refuses
	// RowOrder and RowMove unless RowSort clears it in the same call.
	RowSort  *Sort
	RowOrder []string
	RowMove  *Reorder
}

SetOpts changes the active definition while retaining rows and members. A removed or hidden occupied owned cell is refused. Rename moves the table identity, all retained epochs and its change stream to the new name.

type Sort

type Sort struct {
	By     string
	Desc   bool
	Keep   bool
	Manual bool
}

Sort orders the rows once by name, label, or a column's value (a count, or a text value). Keep makes it standing (name and label only): every row added or rebound later takes its place. Manual ends a standing sort and leaves the rows where they are.

type Summary

type Summary struct {
	Name          string
	Columns, Rows int64
}

Summary is a table's definition width and row count, read with the registry.

func Summaries

func Summaries(ctx context.Context, c redis.Cmdable) ([]Summary, error)

type Table

type Table struct {
	// EpochKey/Field bind this table to a shared epoch domain. An empty key
	// means epoch zero permanently; the default field for a key is "n".
	EpochKey     string
	EpochField   string
	MemberPrefix string
	// Epoch and Revision describe the snapshot returned by the store.
	Epoch       uint64
	Revision    uint64
	Name        string
	Columns     []Column
	FooterLabel string
	Hidden      []string // columns kept, read and used by formulas, but not drawn (set --hide)
	HiddenTable bool     // the whole table kept and read, not drawn by watch (set --hidden / --visible)
	Sort        string   // the standing row sort (row sort --keep): name, -name, label, -label; empty is by hand
	Rows        []Row
	// Props are the table's properties at the snapshot's epoch, name -> value
	// (nil when it has none): values a batch writes with its members, such as
	// a rolling index (L1 contract amendment, table properties).
	Props map[string]string
}

Table is a table as read, or as a caller declares it before binding.

func Read

func Read(ctx context.Context, c redis.Cmdable, name string) (Table, error)

func ReadAt

func ReadAt(ctx context.Context, c redis.Cmdable, name string, epoch uint64) (Table, error)

ReadAt inspects one materialised epoch, including after its template was removed.

func Shape

func Shape(ctx context.Context, c redis.Cmdable, name string) (Table, error)

func (Table) Column

func (t Table) Column(name string) int

Column finds a column by name; -1 when the table has none.

func (Table) Footer

func (t Table) Footer() string

Footer is the footer label, DefaultFooter when unset.

func (Table) HasFooter

func (t Table) HasFooter() bool

HasFooter says some column folds: the render prints a footer row.

func (Table) IsHidden

func (t Table) IsHidden(col string) bool

IsHidden is whether a column is kept but not drawn.

func (Table) Row

func (t Table) Row(key string) int

Row finds a row by key; -1 when the table has none.

type View

type View struct {
	Name    string
	Tables  []string
	Title   string
	Summary string // the count column of the first table the summary line counts as done ("" for no line)
	// State, when set, is the summary line, alone, in place of the counts:
	// the state of whatever fills the view ("STOPPED"). ViewState writes it;
	// ViewSet leaves it as it is.
	State string
}

A view: a named list of tables with a title, read by watch every frame.

func ViewGet

func ViewGet(ctx context.Context, c redis.Cmdable, name string) (View, error)

ViewGet reads a view.

type WriteOptions

type WriteOptions struct {
	Epoch uint64
	Actor string
	Fence string
	Idem  string
	// Receipt receives the committed event without another store exchange.
	Receipt *Receipt
}

WriteOptions binds a mutation to the epoch its caller observed. Omitting options means epoch zero; a stale call is never retried into a new epoch. Actor, Fence and Idem accompany the change event; authorization and the coordinator lease are separate from this table primitive.

Jump to

Keyboard shortcuts

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