package bus import ( "context" "encoding/json" "fmt" "slices" "strings" "git.novox.be/novox/mesh-sdk/go/buscompose" "github.com/nats-io/nats.go/jetstream" ) // SubscribeNew hears one event, named `.`, from the next message on, on an ordered consumer of its own on each // stream that carries it, and hands every message to deliver as the envelope a module sees. The subjects the name lands on are // mesh-sdk's one rule (buscompose.EventSubjects, design 47 G ยง559, A-153), so a name with a wildcard is refused, naming the rule; // a name no stream on the bus carries is an error, never a subscription that hears nothing. stop ends every consumer. func (c *Conn) SubscribeNew(name string, deliver func(json.RawMessage) error) (func(), error) { subjects, err := buscompose.EventSubjects(name, "") if err != nil { return nil, err } js, err := c.JetStream() if err != nil { return nil, err } ctx, cancel := context.WithTimeout(context.Background(), StateTimeout) defer cancel() // The streams that carry each subject, found by the bus: a namespace no stream carries hears nothing and is skipped. byStream := map[string][]string{} for _, subject := range subjects { if strings.HasSuffix(subject, ".>") { continue } stream, err := js.StreamNameBySubject(ctx, subject) if err != nil || stream == "" { continue } byStream[stream] = append(byStream[stream], subject, subject+".>") } if len(byStream) == 0 { return nil, fmt.Errorf("no stream on the bus carries %s (%s): nothing would be heard", name, strings.Join(subjects, ", ")) } var stops []func() stopAll := func() { for _, s := range stops { s() } } for _, stream := range slices.Sorted(mapKeys(byStream)) { cons, err := js.OrderedConsumer(ctx, stream, jetstream.OrderedConsumerConfig{FilterSubjects: byStream[stream], DeliverPolicy: jetstream.DeliverNewPolicy}) if err != nil { stopAll() return nil, fmt.Errorf("an ordered consumer of %s on %s: %w", name, stream, err) } cc, err := cons.Consume(func(m jetstream.Msg) { env, ok := envelopeOf(m.Subject(), m.Headers(), m.Data()) if !ok { return } raw, err := json.Marshal(env) if err != nil { return } if err := deliver(raw); err != nil && c.Logf != nil { c.Logf("[mesh-tools] a subscription for new %s messages did not take one: %v", name, err) } }) if err != nil { stopAll() return nil, fmt.Errorf("consuming %s on %s: %w", name, stream, err) } stops = append(stops, cc.Stop) } return stopAll, nil } func mapKeys(m map[string][]string) func(func(string) bool) { return func(yield func(string) bool) { for k := range m { if !yield(k) { return } } } }