mesh/merge-gate pass: builds mesh-sdk, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript), go/merge-check.sh (go)
mesh/delivery delivered
124 lines
4.2 KiB
Go
124 lines
4.2 KiB
Go
package stdio
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Log — a module's record of operations on the bus, kept as long as the log is (novox/hq ADR 0297). Not the
|
|
// current value: that is state, and a log's state is its fold. Each entry is one operation on one key, its
|
|
// sequence in the log is its identity, and the order of the log is the order of the operations. A module
|
|
// declares each log it owns in its manifest (`logs`) and names it here as it named it there; a log is its
|
|
// owner's alone, and the runtime refuses, with the reason, one it did not issue.
|
|
|
|
// LogEntry is one operation on one key, as the log holds it.
|
|
type LogEntry struct {
|
|
Sequence uint64 `json:"sequence"`
|
|
Key string `json:"key"`
|
|
Value json.RawMessage `json:"value"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// LogPage is one page of a read: its entries, in order, the sequence to read on from (0 when nothing
|
|
// follows) and the log's last sequence when it was read.
|
|
type LogPage struct {
|
|
Entries []LogEntry `json:"entries"`
|
|
Next uint64 `json:"next"`
|
|
Last uint64 `json:"last"`
|
|
}
|
|
|
|
// LogInfo is how much a log holds: its entries and bytes, its cap in bytes, and its first and last sequence.
|
|
type LogInfo struct {
|
|
Entries uint64 `json:"entries"`
|
|
Bytes uint64 `json:"bytes"`
|
|
MaxBytes uint64 `json:"max_bytes"`
|
|
First uint64 `json:"first"`
|
|
Last uint64 `json:"last"`
|
|
}
|
|
|
|
// KeptLog is one log this module owns.
|
|
type KeptLog struct{ name string }
|
|
|
|
// Log is a log this module owns, by its local name.
|
|
func Log(name string) KeptLog { return KeptLog{name: name} }
|
|
|
|
const logTimeout = 30 * time.Second
|
|
|
|
func (l KeptLog) ask(verb string, params map[string]any) (json.RawMessage, error) {
|
|
c, err := live()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params["log"] = l.name
|
|
return c.ask("mesh/log."+verb, params, logTimeout)
|
|
}
|
|
|
|
func (l KeptLog) append(params map[string]any) (uint64, error) {
|
|
raw, err := l.ask("append", params)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var appended struct {
|
|
Sequence uint64 `json:"sequence"`
|
|
}
|
|
return appended.Sequence, json.Unmarshal(raw, &appended)
|
|
}
|
|
|
|
// Append writes an entry for key, whatever came before it, and returns its sequence. A key is one or more
|
|
// names of letters, digits, `-` and `_`, joined by `.`; a value is JSON of at most 256 KiB. A full log
|
|
// refuses the entry: it never drops an older one to make room.
|
|
func (l KeptLog) Append(key string, value any) (uint64, error) {
|
|
return l.append(map[string]any{"key": key, "value": value})
|
|
}
|
|
|
|
// AppendAt writes an entry for key only if the key's last entry is still at expect — the sequence the caller
|
|
// read, 0 for a key with no entry yet — and returns its sequence. When another entry for the key came in
|
|
// between, nothing is written and the error is ErrChanged, as for a lost State update: read again and
|
|
// decide again.
|
|
func (l KeptLog) AppendAt(key string, value any, expect uint64) (uint64, error) {
|
|
return l.append(map[string]any{"key": key, "value": value, "expect": expect})
|
|
}
|
|
|
|
// Read is the entries from the sequence from on, oldest first, at most limit of them (0 is the runtime's
|
|
// page, 100; it gives at most 1000). keys narrows it: a key, with `*` for one name and `>` for the rest;
|
|
// empty is every key. Read on from the page's Next until it is 0.
|
|
func (l KeptLog) Read(from uint64, keys string, limit int) (*LogPage, error) {
|
|
if limit < 0 {
|
|
return nil, fmt.Errorf("a read of %s asks for at least no entries: limit %d", l.name, limit)
|
|
}
|
|
params := map[string]any{"from": from}
|
|
if keys != "" {
|
|
params["keys"] = keys
|
|
}
|
|
if limit > 0 {
|
|
params["limit"] = limit
|
|
}
|
|
raw, err := l.ask("read", params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var p LogPage
|
|
return &p, json.Unmarshal(raw, &p)
|
|
}
|
|
|
|
// Last is a key's last entry, or nil when the key has none.
|
|
func (l KeptLog) Last(key string) (*LogEntry, error) {
|
|
raw, err := l.ask("last", map[string]any{"key": key})
|
|
if err != nil || len(raw) == 0 || string(raw) == "null" {
|
|
return nil, err
|
|
}
|
|
var e LogEntry
|
|
return &e, json.Unmarshal(raw, &e)
|
|
}
|
|
|
|
// Info is how much the log holds and how much it may.
|
|
func (l KeptLog) Info() (*LogInfo, error) {
|
|
raw, err := l.ask("info", map[string]any{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var i LogInfo
|
|
return &i, json.Unmarshal(raw, &i)
|
|
}
|