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