// Package operatorask is a module's asking of a person through the seat that routes asks to the operator (design 47 G // A-101, ยง700, A-74): the seat's holder is read by seatlist.HolderOf, an ask is published under the module's own name, the // router's record of it is read for the warrant, and the router's `decided` is heard. It is shared code, so no module // keeps its own copy; it names no seat, verb or module: the generated contracts' names and types are its parameters. package operatorask import ( "encoding/json" "errors" "fmt" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/seatlist" ) // Caller calls a seat verb or module tool as `seat:.` or `.` and returns its answer // (stdio.Ask is one; so is a process's calls). type Caller = seatlist.HolderCaller // Contract is what the asker takes from the generated contracts: the asking seat and its state of the router's records. type Contract struct { Seat string // StateAsks is the seat's state holding the router's record of each ask. StateAsks string } // Bucket is the bucket of the router's records: the seat's state in the bucket of the seat's holder, as seatlist reads it // (design 47 G A-126). func Bucket(call Caller, c Contract) (string, error) { module, err := seatlist.HolderOf(call, c.Seat) if err != nil { return "", fmt.Errorf("the router's record of an ask is not read: %w", err) } return module + "_" + c.StateAsks, nil } // Asker asks as one module. W is the generated contract's warrant type. type Asker[W any] struct { Module string Contract Contract // Call reaches the seat list's verb. Call Caller // Record reads one record of a bucket under the module's own name (process.Conn.Ask). Record func(bucket, id string) (*stdio.StateEntry, error) // Publish publishes a body de-duplicated by id: the same id twice is one message (stdio.SeatPublish). Publish func(subject string, body any, id string) error // Subscribe hears an event (stdio.Subscribe). Subscribe func(pattern string, handle func(stdio.Envelope) error) error // AskSubject and DecidedSubject are the generated contract's subjects for Module. AskSubject, DecidedSubject func(module string) string } // Ask publishes the ask (the generated contract's Ask) under the module's own name, de-duplicated by its id. func (a *Asker[W]) Ask(id string, ask any) error { if a.Publish == nil || a.AskSubject == nil { return errors.New("the asker has no way to publish") } return a.Publish(a.AskSubject(a.Module), ask, id) } // Where is an ask's state as the router keeps it, and its warrant once there is one; a record not yet there is an empty // state. It is an error while the seat's holder or the record cannot be read. func (a *Asker[W]) Where(id string) (state string, warrant *W, err error) { bucket, err := Bucket(a.Call, a.Contract) if err != nil { return "", nil, err } e, err := a.Record(bucket, id) if err != nil || e == nil { return "", nil, err } var rec struct { State string `json:"state"` Warrant *W `json:"warrant"` } if err := json.Unmarshal(e.Value, &rec); err != nil { return "", nil, fmt.Errorf("the router's record of %s cannot be read: %w", id, err) } return rec.State, rec.Warrant, nil } // Hear hands handle each warrant the router says to this module; a handler's error leaves it for the bus to give again. func (a *Asker[W]) Hear(handle func(W) error) error { if a.Subscribe == nil || a.DecidedSubject == nil { return errors.New("the asker has no way to hear") } return a.Subscribe(a.DecidedSubject(a.Module), func(env stdio.Envelope) error { var w W if err := json.Unmarshal(env.Body, &w); err != nil { return fmt.Errorf("%s holds what is no warrant: %w", env.Key, err) } return handle(w) }) }