Documentation
¶
Overview ¶
Package ntable is a general, Redis-backed table built from one primitive: the ordered set (Glenn 2026-09-27: "at an even simpler level, I think there should be a concept of ordered sets" / "The work stream table is really just a series of ordered sets, per-cell" / "and the value printed, happens to be for each cell, |s|" / "(but it doesn't need to be always)"). It knows nothing about sprints: the sprint table's stream block is its first table (internal/nsprint/table/streams.go), and the nova-table tool is its face.
A table has three kinds of cell (Glenn, the same day: "there are cells that are headers for columns, and cells that are headers for rows" / "and there are cells at the bottom of each row that are sums or some function of the column above." / "for example, for the stream table the bottom rows are the sum of the column above."):
- 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, Glenn's |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
- Variables
- func Add(ctx context.Context, c redis.Cmdable, key, member string, score float64) error
- func Bind(ctx context.Context, c redis.Cmdable, t Table, now time.Time, ...) error
- func Card(ctx context.Context, c redis.Cmdable, key string) (int64, error)
- func CellAdd(ctx context.Context, c redis.Cmdable, name, row, col, member string, ...) (int64, error)
- func CellKey(table, row, col string) string
- func CellKeyAt(table, row, col string, epoch uint64) string
- func CellMove(ctx context.Context, c redis.Cmdable, name, row, from, to, member string, ...) (int64, error)
- func CellRemove(ctx context.Context, c redis.Cmdable, name, row, col, member string, ...) (int64, error)
- func CellText(cols []Column, r Row, j int) string
- func CellsAdd(ctx context.Context, c redis.Cmdable, name, row, col string, score float64, ...) (int64, error)
- func CellsMove(ctx context.Context, c redis.Cmdable, name, row, from, to string, ...) (int64, error)
- func CellsRemove(ctx context.Context, c redis.Cmdable, name, row, col string, members []string, ...) (int64, error)
- func ChangesKey(table string) string
- func Clear(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int64, error)
- func Create(ctx context.Context, c redis.Cmdable, t Table, now time.Time, ...) error
- func DefKey(table string) string
- func Drop(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int, error)
- func DropDefinition(ctx context.Context, c redis.Cmdable, name string, opts ...WriteOptions) (int, error)
- func EpochPrefix(table string, epoch uint64) string
- func FormulaArg(projection string) string
- func IsFormula(projection string) bool
- func List(ctx context.Context, c redis.Cmdable) ([]string, error)
- func MemberCreate(ctx context.Context, c redis.Cmdable, name, id string, opts ...WriteOptions) error
- func MemberKey(id string) string
- func Move(ctx context.Context, c redis.Cmdable, from, to, member string, keepScore bool, ...) error
- func ParseWidths(spec string) (map[string]int, error)
- func Remove(ctx context.Context, c redis.Cmdable, key, member string) error
- func Render(t Table, opts RenderOpts) string
- func RevisionKey(table string) string
- func RowDel(ctx context.Context, c redis.Cmdable, name, key string, opts ...WriteOptions) (bool, error)
- func RowKey(table, row string) string
- func RowKeyAt(table, row string, epoch uint64) string
- func RowSet(ctx context.Context, c redis.Cmdable, name, key string, ...) (int, error)
- func RowsAdd(ctx context.Context, c redis.Cmdable, name string, keys []string, ...) (int, error)
- func RowsAddWithSpec(ctx context.Context, c redis.Cmdable, name string, keys []string, spec RowSpec, ...) (int, error)
- func RowsHide(ctx context.Context, c redis.Cmdable, name string, hide bool, keys []string, ...) (int, error)
- func RowsKey(table string) string
- func RowsKeyAt(table string, epoch uint64) string
- func SameDefinition(a, b Table) bool
- func SameShape(a, b Table) bool
- func Set(ctx context.Context, c redis.Cmdable, name string, change SetOpts, ...) (int, error)
- func ValidName(s string) bool
- func ValidRowKey(s string) bool
- func ValidateColumns(cols []Column) error
- func ViewDelete(ctx context.Context, c redis.Cmdable, name string) (int64, error)
- func ViewList(ctx context.Context, c redis.Cmdable) ([]string, error)
- func ViewSet(ctx context.Context, c redis.Cmdable, v View) error
- type BoundError
- type Cell
- type CellsCmd
- type CheckReport
- type Column
- type CountCmd
- type Member
- type MemberLocation
- type Place
- type ReadCmd
- type Reader
- type Receipt
- type RenderOpts
- type Reorder
- type Row
- type RowSpec
- type SetOpts
- type Sort
- type Summary
- type Table
- type View
- type WriteOptions
Constants ¶
const ( Count = "count" Members = "members" First = "first" Last = "last" Text = "text" )
The projections: what a body cell prints.
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: Glenn 2026-09-27) None = "none" )
The folds: what a footer cell prints over its column.
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" )
DefaultFooter is the footer label a table has when none is set: none (Glenn 2026-09-27: "the footer title should be off by default"); the footer row still prints the folds, with a blank label cell, and `create --footer <label>` names it.
const FnMove = "ns_oset_move"
FnMove is the library function Move calls: ZREM from, ZADD to, in one call, refused NOTMEMBER when the member is not in from (internal/nsprint/fn/lua/table.lua).
const Registry = "tables"
Registry is the SET of every table name.
Variables ¶
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("observed epoch is stale") 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") )
var ErrNoTable = errors.New("no such table")
var ErrNotMember = errors.New("NOTMEMBER")
ErrNotMember is Move's refusal: the member is not in the set it would leave.
Functions ¶
func Bind ¶
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 CellRemove ¶
func CellText ¶
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 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 Drop ¶
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 ¶
EpochPrefix is the storage namespace for one table generation.
func FormulaArg ¶
FormulaArg is the column a pct(<col>) projection names.
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 Move ¶
func Move(ctx context.Context, c redis.Cmdable, from, to, member string, keepScore bool, score float64) error
Move takes member from the set at from and puts it in the set at to, in one library call, keeping its score unless keepScore is false, in which case score is used. ErrNotMember when from does not hold it.
func ParseWidths ¶
ParseWidths reads col=n,col=n.
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 space. An empty table, and a table with no visible row, renders as the empty string with no newline, title or not (Glenn 2026-09-26 10:00 AM ET: an empty stream table is hidden with no extra newline; 2026-09-27: "When a table has no rows, it should automatically hide. When it has rows again, it should show").
The row's label is always the first column (Glenn 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 (Stella'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 RevisionKey ¶
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 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 SameDefinition ¶
SameDefinition says two tables declare the same columns and footer.
func SameShape ¶
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 ValidName ¶
ValidName says a table or column name is one word of letters, digits, '_', '.' and '-', starting with a letter, digit or '_'.
func ValidRowKey ¶
ValidRowKey says a row key is non-empty, valid UTF-8 and holds no ASCII control character.
func ValidateColumns ¶
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 ¶
ViewDelete removes only presentation configuration; tables are untouched.
Types ¶
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 ¶
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 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 ¶
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.
type CheckReport ¶
type CheckReport = typedrec.TableCheck
CheckReport covers both directions of record/set membership in one instant.
type Column ¶
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 ¶
ParseColumn reads one column declaration, name[:projection[:fold[:label]]] (defaults: count, sum for a count column and none otherwise, the name).
func ParseColumns ¶
ParseColumns reads a comma-separated list of column declarations.
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 (internal/nsprint/ws.QueueCardCount) leaves the stream's sentinel out this way.
func QueueCount ¶
QueueCount queues the count of the set at key on pipe, leaving exclude out when it is a member ("" excludes nothing).
type MemberLocation ¶
type MemberLocation = typedrec.TableMember
MemberLocation is an indexed placement in this table, or missing/unplaced.
func MemberFind ¶
MemberFind reads and verifies one member's owned placement atomically. It does not search external bindings; Check audits the complete namespace.
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 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.
type Receipt ¶
Receipt identifies the durable table change. Idem is recorded as caller metadata; this primitive does not deduplicate attempts.
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
// HideZeroRows hides a row whose count cells are all zero and all read.
HideZeroRows bool
// Title is the table's name, printed in the top-left cell, the header
// of the row-label column (Glenn 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 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.
type SetOpts ¶
type SetOpts struct {
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 ¶
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 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
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
}
Table is a table as read, or as a caller declares it before binding.
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)
}
A view: a named list of tables with a title, read by watch every frame.
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.