Main reverted G's half merge (#189, reverted by8afc3ebin #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 headf30dee54only 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 atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
92 lines
3.7 KiB
Go
92 lines
3.7 KiB
Go
package views
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// 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 the seat event the config's Asked subject names, 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) {
|
|
return NewWriter(withStdioPlumbing(cfg, emit, subscribe))
|
|
}
|
|
|
|
// NewStdioHeldWriter is NewHeldWriter for a stdio module: it fills `Publish` and `Listen` as NewStdioWriter does (design 47
|
|
// G A-37, after A-16).
|
|
func NewStdioHeldWriter[F, C, S, A any](cfg WriterConfig[F, C, S, A]) (*HeldWriter[F, C, S, A], error) {
|
|
return NewHeldWriter(withStdioPlumbing(cfg, stdio.Emit, stdio.SubscribeNew))
|
|
}
|
|
|
|
func withStdioPlumbing[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) WriterConfig[F, C, S, A] {
|
|
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, subject string, handle func([]byte)) (func(), error) {
|
|
event, err := seatEventOfSubject(subject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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 cfg
|
|
}
|
|
|
|
// eventOfSubject is the event of a subject of the writer: the old stream's `mesh.stream.<module>.event.<event>.<node>` or the
|
|
// binding's `mesh.bus.<module>.finding-events.<event>.<node>` (design 47 G A-274).
|
|
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
|
|
}
|
|
}
|
|
if len(p) == 6 && p[0] == "mesh" && p[1] == "bus" && p[3] == FindingEventsLocal {
|
|
return p[4], nil
|
|
}
|
|
return "", fmt.Errorf("the subject %q is not of the form mesh.stream.<module>.event.<event>.<node> or mesh.bus.<module>.finding-events.<event>.<node>", subject)
|
|
}
|
|
|
|
// seatEventOfSubject is `<seat>.<event>` of a seat event's subject `mesh.seat.<seat>.event.<event>`, the name a runtime
|
|
// subscribes a seat's event by.
|
|
func seatEventOfSubject(subject string) (string, error) {
|
|
p := strings.Split(subject, ".")
|
|
if len(p) == 5 && p[0] == "mesh" && p[1] == "seat" && p[3] == "event" {
|
|
return p[2] + "." + p[4], nil
|
|
}
|
|
return "", fmt.Errorf("the subject %q is not of the form mesh.seat.<seat>.event.<event>", subject)
|
|
}
|