Files
mesh-sdk/go/process/seatstream.go
T
jschoubben 769dcf16e3 R-3: W1.12: a module hears a seat's event kept on its own stream through its durable
A seat's event kept apart from its family (the node-engine seat's running, kept last per subject; mesh-delivery's owed,
a stream with an age and a cap) had no helper: EventHub binds only the shared events stream, and dev-G-engine and
dev-G-forge each met it. process.SeatStreamName is the bus's name of a contract's Stream<E> (SEAT_<seat>_<event>), one
rule for its maker and its hearers; Account.SeatEventConsumerConfig the module's durable as the maker makes it (last
per subject where the event is kept so); Account.HearSeatEvents binds and pulls it, making nothing.

Builds: design 47 G §260, §24, §39, §146, §153, §199

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:51:06 +01:00

61 lines
3.2 KiB
Go

package process
import (
"context"
"fmt"
"strings"
"github.com/nats-io/nats.go/jetstream"
)
// A seat's event kept in a stream of its own (seatdef's `keep`: the last message of each subject, or a stream with an age
// and a cap), not on the shared events stream (novox/hq design 47: the node-engine seat's `running.<node>.<module>`, the
// seat mesh-delivery's `owed`). A generated contract names that stream as `<seat>.<event>` (Stream<E>, seatdef.KeptStream);
// these are the one rule, for the part that makes the stream and its consumers (the grants part, from each module's
// `consumes`) and for every part that hears it, of the stream's name on the bus and the module's durable on it.
// SeatStreamPrefix starts the bus's name of every stream a seat's event is kept in apart from its family.
const SeatStreamPrefix = "SEAT_"
// SeatStreamName is the bus's name of the stream a contract names kept (`<seat>.<event>`): the bus takes no dot in a
// stream's name, so `SEAT_<seat>_<event>`, each dot an underscore (`node-engine.running` is `SEAT_node-engine_running`).
func SeatStreamName(kept string) string {
return SeatStreamPrefix + strings.ReplaceAll(kept, ".", "_")
}
// SeatEventConsumerConfig is the module's durable on a seat event's own stream as its maker makes it: <node>_<module>, as
// on the shared stream, filtered to the subjects the module consumes, acknowledged one by one; for an event kept last
// per subject it starts from the last message of each subject, never from now, so a consumer made after a subject was
// last said still hears it, and two parts' consumers made at different times hold the same (design 47).
func (a Account) SeatEventConsumerConfig(subjects []string, lastPerSubject bool) jetstream.ConsumerConfig {
cfg := jetstream.ConsumerConfig{Durable: a.EventConsumerName(), AckPolicy: jetstream.AckExplicitPolicy, FilterSubjects: subjects}
if lastPerSubject {
cfg.DeliverPolicy = jetstream.DeliverLastPerSubjectPolicy
}
return cfg
}
// HearSeatEvents binds the module's durable on the stream of kept (a contract's Stream<E>) and pulls it, handing each
// message to handle, which acknowledges it; every error the bus reports while hearing is said through logf. Like
// EventHub it makes neither the stream nor the consumer: one the bus does not hold yet is this call's error, naming
// both, and a later call binds it. The caller stops the returned context.
func (a Account) HearSeatEvents(ctx context.Context, js jetstream.JetStream, kept string, handle func(jetstream.Msg), logf func(string, ...any)) (jetstream.ConsumeContext, error) {
if handle == nil {
return nil, fmt.Errorf("hearing %s needs a handler", kept)
}
if logf == nil {
logf = func(string, ...any) {}
}
stream := SeatStreamName(kept)
cons, err := a.EventConsumer(ctx, js, stream)
if err != nil {
return nil, fmt.Errorf("the consumer %s on %s (the seat's stream %s) is not there: %w", a.EventConsumerName(), stream, kept, err)
}
cc, err := HearEvents(cons, handle, logf)
if err != nil {
return nil, fmt.Errorf("the consumer %s on %s (the seat's stream %s) is not heard: %w", a.EventConsumerName(), stream, kept, err)
}
logf("events: the consumer %s on %s (the seat's stream %s) is heard", a.EventConsumerName(), stream, kept)
return cc, nil
}