116 lines
3.8 KiB
Go
116 lines
3.8 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/pernode"
|
|
)
|
|
|
|
// A state its owner keeps one bucket per node (novox/hq ADR 0593 §6: `composed-<node>`), read as one state on this
|
|
// connection: the rules are pernode's, this connection only adapts to its Bus, opening a bucket as State does (a bucket
|
|
// not granted refused promptly, naming it).
|
|
|
|
// What a reader of a per-node state is granted beside each bucket's read (pernode.ListSubject, CreatedAdvisory).
|
|
const (
|
|
FamilyListSubject = pernode.ListSubject
|
|
FamilyCreatedAdvisory = pernode.CreatedAdvisory
|
|
)
|
|
|
|
// NodeFamily is a per-node state read across every node's bucket on this connection.
|
|
type NodeFamily struct {
|
|
Read string
|
|
Prefix string
|
|
c *Conn
|
|
}
|
|
|
|
// ReadPerNode is another module's state kept one bucket per node, named read in the reader's manifest, its buckets
|
|
// prefix followed by the node's name.
|
|
func (c *Conn) ReadPerNode(read, prefix string) NodeFamily {
|
|
return NodeFamily{Read: read, Prefix: prefix, c: c}
|
|
}
|
|
|
|
// connBus is pernode.Bus over this connection.
|
|
type connBus struct {
|
|
pernode.Bus
|
|
f NodeFamily
|
|
}
|
|
|
|
func (b connBus) OpenBucket(ctx context.Context, name string) (jetstream.KeyValue, error) {
|
|
module, state, _ := cutRead(b.f.Read)
|
|
return b.f.c.bound(module, state, name, func() error { return nil }).kv(ctx)
|
|
}
|
|
|
|
func cutRead(read string) (string, string, bool) { return strings.Cut(read, ".") }
|
|
|
|
func (f NodeFamily) family() pernode.Family {
|
|
return pernode.Family{Read: f.Read, Prefix: f.Prefix, Bus: connBus{Bus: pernode.OverNATS(f.c.nc), f: f}}
|
|
}
|
|
|
|
// Nodes are the nodes whose bucket of the family the bus holds now, sorted.
|
|
func (f NodeFamily) Nodes() ([]string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
|
defer cancel()
|
|
return f.family().Nodes(ctx)
|
|
}
|
|
|
|
// Node is the state of one node of the family, its bucket `<prefix><node>`, bound as Read binds a bucket.
|
|
func (f NodeFamily) Node(node string) State {
|
|
module, name, _ := cutRead(f.Read)
|
|
bucket, err := f.family().Bucket(node)
|
|
return f.c.bound(module, name, bucket, func() error { return err })
|
|
}
|
|
|
|
// Get is a key's current value, read from the bucket of the node the key names, or nil when it has none or that node
|
|
// has no bucket.
|
|
func (f NodeFamily) Get(key string) (*stdio.StateEntry, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
|
defer cancel()
|
|
e, err := f.family().Get(ctx, key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return &stdio.StateEntry{Key: e.Key(), Value: json.RawMessage(e.Value()), Revision: e.Revision()}, nil
|
|
}
|
|
|
|
// Keys is every key with a current value in every bucket of the family, sorted.
|
|
func (f NodeFamily) Keys() ([]string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
|
defer cancel()
|
|
return f.family().Keys(ctx)
|
|
}
|
|
|
|
// Watch is pernode.Family.Watch on this connection, each change handed as a State change: every bucket's current values,
|
|
// then its changes, a bucket created later included, one call at a time; the connection closing ends it.
|
|
func (f NodeFamily) Watch(key string, handle func(stdio.StateChange) error) error {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() {
|
|
for !f.c.nc.IsClosed() {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-f.c.nc.StatusChanged():
|
|
}
|
|
}
|
|
cancel()
|
|
}()
|
|
_, name, _ := cutRead(f.Read)
|
|
err := f.family().Watch(ctx, key, func(c pernode.Change) error {
|
|
sc := stdio.StateChange{State: name, Key: c.Entry.Key(), Op: stdio.OpPut, Revision: c.Entry.Revision(), Current: c.Current}
|
|
if c.Entry.Operation() == jetstream.KeyValuePut {
|
|
sc.Value = json.RawMessage(c.Entry.Value())
|
|
} else {
|
|
sc.Op = stdio.OpDelete
|
|
}
|
|
return handle(sc)
|
|
})
|
|
if err != nil {
|
|
cancel()
|
|
}
|
|
return err
|
|
}
|