Files
mesh-sdk/go/views/stdio_writer.go
T
jschoubben 58bbc26219 G V-8 (TS), A-17, test: the TypeScript writer is generic over the contract's bodies and names its events; findings-asked is mesh-conditions' event
The TypeScript writer holds no FindingBody of its own and works on the wire form (checkFinding, views reader findings laid out by
viewFindingBody); both writers name findings-asked as the seat mesh-conditions' event. The view-waits test waits on the raise,
not on a count of yields.

Builds: design 47 G §686, §640, §687, A-3, A-13, A-17
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:45:07 +01:00

67 lines
2.6 KiB
Go

package views
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// FindingsAskedEvent is the event `findings-asked`, always the seat mesh-conditions' event, named with that seat (design 47 G
// A-17); a stdio module's writer subscribes to it by this name, and no module supplies it.
const FindingsAskedEvent = "mesh-conditions.findings-asked"
// NewStdioWriter makes the finding writer of a stdio module (design 47 G A-16, §461, §485, §503): it fills `Publish` from
// the module's own event publish (stdio.Emit, answered once the bus acknowledged it) and `Listen` from its subscription
// (stdio.SubscribeNew of FindingsAskedEvent, only the asks published after it is made), so no module writes that adapter. A `Publish` or `Listen`
// the config already holds is kept. The runtime publishes and subscribes by the event's name on the subject the module's
// membership names for it, so each of the writer's four subjects (FindingSubjects) is told by its event.
func NewStdioWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A]) (*Writer[F, C, S, A], error) {
return newStdioWriter(cfg, stdio.Emit, stdio.SubscribeNew)
}
func newStdioWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A],
emit func(string, any, ...stdio.EmitOptions) error,
subscribe func(string, func(stdio.Envelope) error) error) (*Writer[F, C, S, A], error) {
if cfg.Publish == nil {
cfg.Publish = func(_ context.Context, subject string, data []byte, id string) error {
event, err := eventOfSubject(subject)
if err != nil {
return err
}
return emit(event, json.RawMessage(data), stdio.EmitOptions{MessageID: id, Ack: true})
}
}
if cfg.Listen == nil {
cfg.Listen = func(_ context.Context, _ string, handle func([]byte)) (func(), error) {
event := FindingsAskedEvent
var stopped atomic.Bool
// the runtime keeps a subscription until the process ends: a stopped listener hands on nothing
if err := subscribe(event, func(env stdio.Envelope) error {
if !stopped.Load() {
handle(env.Body)
}
return nil
}); err != nil {
return nil, fmt.Errorf("subscribing to %s: %w", event, err)
}
return func() { stopped.Store(true) }, nil
}
}
return NewWriter(cfg)
}
// eventOfSubject is the event of a subject of FindingSubjects: `mesh.stream.<module>.event.<event>.<node>`.
func eventOfSubject(subject string) (string, error) {
p := strings.Split(subject, ".")
for i, s := range p {
if s == "event" && i+2 == len(p)-1 && i > 0 {
return p[i+1], nil
}
}
return "", fmt.Errorf("the subject %q is not of the form mesh.stream.<module>.event.<event>.<node>", subject)
}