ntable

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 12 Imported by: 0

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

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: Glenn 2026-09-27)
	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"
)
View Source
const DefaultFooter = ""

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.

View Source
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).

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("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")
)
View Source
var ErrNoTable = errors.New("no such table")
View Source
var ErrNotMember = errors.New("NOTMEMBER")

ErrNotMember is Move's refusal: the member is not in the set it would leave.

Functions

func Add

func Add(ctx context.Context, c redis.Cmdable, key, member string, score float64) error

Add puts member into the set at key with score (ZADD).

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 Card

func Card(ctx context.Context, c redis.Cmdable, key string) (int64, error)

Card is the set's cardinality (ZCARD).

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 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 column a pct(<col>) projection names.

func IsFormula

func IsFormula(projection string) bool

IsFormula is whether a projection is computed rather than read.

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 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

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

ParseWidths reads col=n,col=n.

func Remove

func Remove(ctx context.Context, c redis.Cmdable, key, member string) error

Remove takes member out of the set at key (ZREM); a member not there is not an error.

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 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 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 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 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.

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

type Cell struct {
	Key     string
	Bound   bool
	Count   int64
	Members []Member
	Unread  bool
}

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

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 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 (internal/nsprint/ws.QueueCardCount) 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.

func (*CountCmd) Val

func (c *CountCmd) Val() int64

Val is Result's count, 0 on an error.

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)

func MembersOf

func MembersOf(ctx context.Context, c redis.Cmdable, key string) ([]Member, error)

MembersOf is every member of the set at key in score order (ZRANGE WITHSCORES).

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 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 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 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
}

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 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 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
}

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)
}

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