Files
mesh-sdk/go/views/held_writer.go
T
jschoubben 5fa64be894 Revert the revert of G's mesh-sdk merge (#191): G's sdk stands again over reader release 3
Main reverted G's half merge (#189, reverted by 8afc3eb in #191) and then took the reader release's S1-S5, which
copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the
release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's
empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's head f30dee54 only by those and
the release's two tests (objects cap option, seat states and stream kinds).

Builds: design 47 G (the milestone's sdk part, as at f30dee54)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 12:49:56 +01:00

186 lines
6.0 KiB
Go

package views
import (
"context"
"encoding/json"
"errors"
"sort"
"sync"
)
// HeldWriter is the finding writer of a part whose findings are raised as they happen, not judged afresh (design 47 G A-29,
// §686): it holds the findings it has written, seeded from the keys of its state `findings` before it starts, under a lock of
// its own (never the part's: the writer calls Observe under its own lock while the part writes under its), and its Observe
// answers them, so a statement says exactly what the part holds. Write and Clear update the held set and then the writer's.
// It is a Writer[F, C, S, A] with that Observe, so no part writes the adapter.
type HeldWriter[F, C, S, A any] struct {
*Writer[F, C, S, A]
mu sync.Mutex
held map[string]F
state FindingState
started bool
pending map[string]F // written before Start, written through the writer at Start
cleared map[string][4]string // cleared before Start, cleared through the writer at Start
// pends are the keys the part cannot judge yet since its start (design 47 G §384): a kept call it has not yet made
// again, named in every statement until the part judges it.
pends map[string]PendingKey
}
// NewHeldWriter makes the writer for cfg, whose Observe is none (a given Observe is refused): the findings that stand in
// cfg.State are read at once, each key's value as the writer kept it (a value that is no finding of F is left to the next
// statement, which deletes its key when it is of a kind the writer owns).
func NewHeldWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A]) (*HeldWriter[F, C, S, A], error) {
if cfg.Observe != nil {
return nil, errors.New("a held finding writer observes what it holds: it takes no Observe")
}
if cfg.State == nil {
return nil, errors.New("a finding writer needs the state `findings`")
}
h := &HeldWriter[F, C, S, A]{held: map[string]F{}, pends: map[string]PendingKey{}}
h.state, h.pending, h.cleared = cfg.State, map[string]F{}, map[string][4]string{}
cfg.Observe = func(context.Context) (Observed[F], error) {
h.mu.Lock()
defer h.mu.Unlock()
var o Observed[F]
for _, f := range h.held {
o.Findings = append(o.Findings, f)
}
for _, p := range h.pends {
o.Pending = append(o.Pending, p)
}
sort.Slice(o.Pending, func(i, j int) bool { return o.Pending[i].Key < o.Pending[j].Key })
return o, nil
}
w, err := NewWriter(cfg)
if err != nil {
return nil, err
}
h.Writer = w
return h, nil
}
// Write holds the finding and writes it through the writer.
func (h *HeldWriter[F, C, S, A]) Write(ctx context.Context, finding F) error {
m, err := wireOf(&finding)
if err != nil {
return err
}
if err := CheckFinding(m); err != nil {
return err
}
if !h.Writer.owns(str(m, "kind")) {
return h.Writer.Write(ctx, finding) // refused by the writer, naming the kind, and held no more than it is
}
h.mu.Lock()
h.held[keyOf(m)] = finding
if !h.started {
h.pending[keyOf(m)] = finding
delete(h.cleared, keyOf(m))
h.mu.Unlock()
return nil
}
h.mu.Unlock()
return h.Writer.Write(ctx, finding)
}
// Clear stops holding the finding of scope, id and kind and clears it through the writer.
func (h *HeldWriter[F, C, S, A]) Clear(ctx context.Context, scope, id, kind, why string) error {
h.mu.Lock()
k := FindingKey(scope, id, kind)
delete(h.held, k)
if !h.started {
delete(h.pending, k)
h.cleared[k] = [4]string{scope, id, kind, why}
h.mu.Unlock()
return nil
}
h.mu.Unlock()
return h.Writer.Clear(ctx, scope, id, kind, why)
}
// WriteWire is Write of a finding in its wire form, held as Write holds it (design 47 G A-37): a views reader writing through
// the held writer has its findings in the held set, so in the statement.
func (h *HeldWriter[F, C, S, A]) WriteWire(ctx context.Context, finding map[string]any) error {
var f F
if err := fillFrom(&f, finding); err != nil {
return err
}
return h.Write(ctx, f)
}
// Pend names a key the part cannot judge yet since its start, with its since and its bound, in every statement until Judged
// (design 47 G §384; design 46 7-approval-mark §16: an acted ask whose hand-act is not yet asked again). A key pended again
// keeps its first since.
func (h *HeldWriter[F, C, S, A]) Pend(p PendingKey) {
h.mu.Lock()
defer h.mu.Unlock()
if was, ok := h.pends[p.Key]; ok {
p.Since = was.Since
}
h.pends[p.Key] = p
}
// Judged ends a key's pending: the part judged it, raising or clearing its finding as it found.
func (h *HeldWriter[F, C, S, A]) Judged(key string) {
h.mu.Lock()
defer h.mu.Unlock()
delete(h.pends, key)
}
// Held answers the findings the writer holds, a copy taken under its lock (design 47 G A-38), so a part reads its open findings
// from the writer.
func (h *HeldWriter[F, C, S, A]) Held() []F {
h.mu.Lock()
defer h.mu.Unlock()
out := make([]F, 0, len(h.held))
for _, f := range h.held {
out = append(out, f)
}
return out
}
// Start seeds what the writer holds from the keys of its state `findings` (design 47 G A-40), what was written before it
// standing over them, writes what was written and clears what was cleared before it, and starts the writer. A part makes the
// writer before its module serves, so nothing is read from the state until now.
func (h *HeldWriter[F, C, S, A]) Start(ctx context.Context) error {
keys, err := h.state.Keys()
if err != nil {
return err
}
h.mu.Lock()
for _, k := range keys {
e, err := h.state.Get(k)
if err != nil {
h.mu.Unlock()
return err
}
if _, ok := h.held[k]; ok {
continue
}
if _, ok := h.cleared[k]; ok {
continue
}
var f F
if e == nil || json.Unmarshal(e.Value, &f) != nil {
continue
}
if m, err := wireOf(&f); err == nil && keyOf(m) == k {
h.held[k] = f
}
}
pending, cleared := h.pending, h.cleared
h.started, h.pending, h.cleared = true, nil, nil
h.mu.Unlock()
for _, f := range pending {
if err := h.Writer.Write(ctx, f); err != nil {
return err
}
}
for _, c := range cleared {
if err := h.Writer.Clear(ctx, c[0], c[1], c[2], c[3]); err != nil {
return err
}
}
return h.Writer.Start(ctx)
}