Read a ledger: an instance's state, its history oldest first with sequence and author, a watch of its states, a declared index (issue 1966 T3)

This commit is contained in:
2026-11-03 13:42:33 +01:00
parent ad22c5e9fe
commit 63c1e3faa2
6 changed files with 390 additions and 2 deletions
+114
View File
@@ -5,9 +5,12 @@ import (
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/buscalls"
"git.novox.be/novox/mesh-sdk/go/membership"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -227,3 +230,114 @@ func (s *busSnapshots) Put(key string, value []byte, expect uint64) error {
_, err := publishExpecting(s.write, s.prefix+"."+key, value, expect)
return err
}
// Watch hands deliver every instance's record under a key pattern as it is, then each change; a removed key is handed as
// nil.
func (s *busState) Watch(pattern string, deliver func(key string, value []byte, current bool) error) (func(), error) {
ctx, cancel := context.WithTimeout(context.Background(), StateTimeout)
kv, err := s.kv(ctx)
cancel()
if err != nil {
return nil, err
}
w, err := kv.Watch(context.Background(), pattern)
if err != nil {
return nil, err
}
hand := func(e jetstream.KeyValueEntry, current bool) {
var value []byte
if e.Operation() == jetstream.KeyValuePut {
value = e.Value()
}
if err := deliver(e.Key(), value, current); err != nil {
s.c.Logf("[mesh-tools] a ledger watch of %s was not taken: %v", s.bucket, err)
}
}
timer := time.NewTimer(StateTimeout)
defer timer.Stop()
for current := true; current; {
select {
case e, ok := <-w.Updates():
if !ok {
return nil, errors.New("the ledger's state watch ended before its current states were handed over")
}
if e == nil {
current = false
continue
}
hand(e, true)
case <-timer.C:
_ = w.Stop()
return nil, fmt.Errorf("the ledger's state %s did not hand its current states in %s", s.bucket, StateTimeout)
}
}
go func() {
for e := range w.Updates() {
if e != nil {
hand(e, false)
}
}
}()
return func() { _ = w.Stop() }, nil
}
// LedgerIndexes are the buckets of a ledger binding's declared indexes, by index name, on the runner's own connection.
func (c *Conn) LedgerIndexes(b membership.BusBinding) map[string]ledger.Index {
out := map[string]ledger.Index{}
for name, bucket := range b.Indexes {
out[name] = &busState{c: c, bucket: bucket}
}
return out
}
func (s *busState) Entries(pattern string, limit int) ([]buscalls.IndexEntry, error) {
if pattern == "" {
pattern = ">"
}
keys, err := s.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
var out []buscalls.IndexEntry
for _, k := range keys {
if limit > 0 && len(out) >= limit {
break
}
if !SubjectMatches(pattern, k) {
continue
}
v, _, err := s.Get(k)
if err != nil {
return out, err
}
if v != nil {
out = append(out, buscalls.IndexEntry{Key: k, Value: valueOf(v)})
}
}
return out, nil
}
func (s *busState) Put(key string, value []byte) error {
ctx, cancel := context.WithTimeout(context.Background(), StateTimeout)
defer cancel()
kv, err := s.kv(ctx)
if err != nil {
return err
}
_, err = kv.Put(ctx, key, value)
return err
}
func (s *busState) Delete(key string) error {
ctx, cancel := context.WithTimeout(context.Background(), StateTimeout)
defer cancel()
kv, err := s.kv(ctx)
if err != nil {
return err
}
if err := kv.Delete(ctx, key); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
return err
}
return nil
}