refactor
This commit is contained in:
+63
-13
@@ -54,42 +54,78 @@ type HookInfo struct {
|
||||
// — so observability/notification hooks can't break the business transaction.
|
||||
type Hook func(ctx context.Context, st *State, info HookInfo, params json.RawMessage) error
|
||||
|
||||
// CapabilityMeta is the editor-facing documentation attached at registration
|
||||
// time. The server generates `/sagas/capabilities` from these registrations, so
|
||||
// UIs should consume this payload instead of hardcoding per-tool help.
|
||||
//
|
||||
// Register every action/predicate/hook with the matching *WithMeta helper when
|
||||
// it should be discoverable in editors. ExampleParams is arbitrary JSON shown as
|
||||
// a starter payload for params-capable tools.
|
||||
type CapabilityMeta struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
ExampleParams json.RawMessage `json:"exampleParams,omitempty"`
|
||||
}
|
||||
|
||||
// Registry holds the named actions, predicates and hooks a flow can reference.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
actions map[string]Action
|
||||
predicates map[string]Predicate
|
||||
hooks map[string]Hook
|
||||
mu sync.RWMutex
|
||||
actions map[string]Action
|
||||
actionMeta map[string]CapabilityMeta
|
||||
predicates map[string]Predicate
|
||||
predicateMeta map[string]CapabilityMeta
|
||||
hooks map[string]Hook
|
||||
hookMeta map[string]CapabilityMeta
|
||||
}
|
||||
|
||||
// NewRegistry returns an empty registry.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
actions: map[string]Action{},
|
||||
predicates: map[string]Predicate{},
|
||||
hooks: map[string]Hook{},
|
||||
actions: map[string]Action{},
|
||||
actionMeta: map[string]CapabilityMeta{},
|
||||
predicates: map[string]Predicate{},
|
||||
predicateMeta: map[string]CapabilityMeta{},
|
||||
hooks: map[string]Hook{},
|
||||
hookMeta: map[string]CapabilityMeta{},
|
||||
}
|
||||
}
|
||||
|
||||
// Action registers an action under name (overwrites an existing one).
|
||||
func (r *Registry) Action(name string, fn Action) {
|
||||
r.ActionWithMeta(name, fn, CapabilityMeta{})
|
||||
}
|
||||
|
||||
// ActionWithMeta registers an action plus the metadata editors should display.
|
||||
func (r *Registry) ActionWithMeta(name string, fn Action, meta CapabilityMeta) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.actions[name] = fn
|
||||
r.actionMeta[name] = meta
|
||||
}
|
||||
|
||||
// Predicate registers a predicate under name.
|
||||
func (r *Registry) Predicate(name string, fn Predicate) {
|
||||
r.PredicateWithMeta(name, fn, CapabilityMeta{})
|
||||
}
|
||||
|
||||
// PredicateWithMeta registers a predicate plus the metadata editors should display.
|
||||
func (r *Registry) PredicateWithMeta(name string, fn Predicate, meta CapabilityMeta) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.predicates[name] = fn
|
||||
r.predicateMeta[name] = meta
|
||||
}
|
||||
|
||||
// Hook registers a hook under name.
|
||||
func (r *Registry) Hook(name string, fn Hook) {
|
||||
r.HookWithMeta(name, fn, CapabilityMeta{})
|
||||
}
|
||||
|
||||
// HookWithMeta registers a hook plus the metadata editors should display.
|
||||
func (r *Registry) HookWithMeta(name string, fn Hook, meta CapabilityMeta) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.hooks[name] = fn
|
||||
r.hookMeta[name] = meta
|
||||
}
|
||||
|
||||
func (r *Registry) action(name string) (Action, bool) {
|
||||
@@ -116,9 +152,12 @@ func (r *Registry) hook(name string) (Hook, bool) {
|
||||
// Capabilities is the set of registered names, surfaced so an editor UI can
|
||||
// offer exactly the actions/predicates/hooks a flow may reference.
|
||||
type Capabilities struct {
|
||||
Actions []string `json:"actions"`
|
||||
Predicates []string `json:"predicates"`
|
||||
Hooks []string `json:"hooks"`
|
||||
Actions []string `json:"actions"`
|
||||
ActionMeta map[string]CapabilityMeta `json:"actionMeta,omitempty"`
|
||||
Predicates []string `json:"predicates"`
|
||||
PredicateMeta map[string]CapabilityMeta `json:"predicateMeta,omitempty"`
|
||||
Hooks []string `json:"hooks"`
|
||||
HookMeta map[string]CapabilityMeta `json:"hookMeta,omitempty"`
|
||||
}
|
||||
|
||||
// Capabilities returns the sorted registered names.
|
||||
@@ -126,12 +165,23 @@ func (r *Registry) Capabilities() Capabilities {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return Capabilities{
|
||||
Actions: sortedKeys(r.actions),
|
||||
Predicates: sortedKeys(r.predicates),
|
||||
Hooks: sortedKeys(r.hooks),
|
||||
Actions: sortedKeys(r.actions),
|
||||
ActionMeta: cloneMeta(r.actionMeta),
|
||||
Predicates: sortedKeys(r.predicates),
|
||||
PredicateMeta: cloneMeta(r.predicateMeta),
|
||||
Hooks: sortedKeys(r.hooks),
|
||||
HookMeta: cloneMeta(r.hookMeta),
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMeta(in map[string]CapabilityMeta) map[string]CapabilityMeta {
|
||||
out := make(map[string]CapabilityMeta, len(in))
|
||||
for k, v := range in {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sortedKeys[V any](m map[string]V) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
|
||||
+11
-3
@@ -13,9 +13,17 @@ import (
|
||||
// (structured log line) and "webhook" (HTTP POST of the event). Domain packages
|
||||
// register their own hooks (e.g. "amqp_emit") on the same registry.
|
||||
func RegisterBuiltinHooks(reg *Registry) {
|
||||
reg.Hook("log", logHook)
|
||||
reg.Hook("noop", func(context.Context, *State, HookInfo, json.RawMessage) error { return nil })
|
||||
reg.Hook("webhook", webhookHook(&http.Client{Timeout: 10 * time.Second}))
|
||||
reg.HookWithMeta("log", logHook, CapabilityMeta{
|
||||
Description: "Write a structured log line for the current step and phase.",
|
||||
ExampleParams: json.RawMessage(`{"message":"payment captured"}`),
|
||||
})
|
||||
reg.HookWithMeta("noop", func(context.Context, *State, HookInfo, json.RawMessage) error { return nil }, CapabilityMeta{
|
||||
Description: "Do nothing. Useful as a placeholder while shaping a flow.",
|
||||
})
|
||||
reg.HookWithMeta("webhook", webhookHook(&http.Client{Timeout: 10 * time.Second}), CapabilityMeta{
|
||||
Description: "POST the flow event payload to an external HTTP endpoint.",
|
||||
ExampleParams: json.RawMessage(`{"url":"https://example.test/order-events","headers":{"X-Flow":"place-and-pay"}}`),
|
||||
})
|
||||
}
|
||||
|
||||
// logHook emits a structured log line for the step/phase. Params (optional):
|
||||
|
||||
Reference in New Issue
Block a user