From 96f1ceb3623f128d0e3a509438075d22bc7c4482 Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 3 Mar 2026 12:54:36 -0500 Subject: [PATCH] Finished event to subgraph conversion. Wrote expander pattern for custom rules. --- event.go | 7 +++- event_test.go | 70 +++++++++++++++++++++++++++++++++++++++- expanders.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ graph.go | 10 ++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 expanders.go diff --git a/event.go b/event.go index aa5a803..bd0eb09 100644 --- a/event.go +++ b/event.go @@ -4,7 +4,7 @@ import ( roots "git.wisehodl.dev/jay/go-roots/events" ) -func EventToSubgraph(e roots.Event) *Subgraph { +func EventToSubgraph(e roots.Event, exp ExpanderRegistry) *Subgraph { subgraph := NewSubgraph() // Create Event node @@ -45,6 +45,11 @@ func EventToSubgraph(e roots.Event) *Subgraph { subgraph.AddRel(rel) } + // Run expanders + for _, expander := range exp { + expander(e, subgraph) + } + return subgraph } diff --git a/event_test.go b/event_test.go index 6a6e1a9..2ff5b26 100644 --- a/event_test.go +++ b/event_test.go @@ -83,11 +83,79 @@ func TestEventToSubgraph(t *testing.T) { return s }(), }, + { + name: "e tag with valid hex64", + event: roots.Event{ + ID: ids["a"], PubKey: ids["b"], + CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content, + Tags: []roots.Tag{{"e", ids["c"]}}, + }, + expected: func() *Subgraph { + s, eventNode, _ := baseSubgraph(ids["a"], ids["b"]) + tagNode := NewTagNode("e", ids["c"]) + referencedEvent := NewEventNode(ids["c"]) + s.AddNode(tagNode) + s.AddNode(referencedEvent) + s.AddRel(NewTaggedRel(eventNode, tagNode, nil)) + s.AddRel(NewReferencesEventRel(tagNode, referencedEvent, nil)) + return s + }(), + }, + { + name: "e tag with invalid value", + event: roots.Event{ + ID: ids["a"], PubKey: ids["b"], + CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content, + Tags: []roots.Tag{{"e", "notvalid"}}, + }, + expected: func() *Subgraph { + s, eventNode, _ := baseSubgraph(ids["a"], ids["b"]) + tagNode := NewTagNode("e", "notvalid") + s.AddNode(tagNode) + s.AddRel(NewTaggedRel(eventNode, tagNode, nil)) + return s + }(), + }, + { + name: "p tag with valid hex64", + event: roots.Event{ + ID: ids["a"], PubKey: ids["b"], + CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content, + Tags: []roots.Tag{{"p", ids["d"]}}, + }, + expected: func() *Subgraph { + s, eventNode, _ := baseSubgraph(ids["a"], ids["b"]) + tagNode := NewTagNode("p", ids["d"]) + referencedUser := NewUserNode(ids["d"]) + s.AddNode(tagNode) + s.AddNode(referencedUser) + s.AddRel(NewTaggedRel(eventNode, tagNode, nil)) + s.AddRel(NewReferencesUserRel(tagNode, referencedUser, nil)) + return s + }(), + }, + { + name: "p tag with invalid value", + event: roots.Event{ + ID: ids["a"], PubKey: ids["b"], + CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content, + Tags: []roots.Tag{{"p", "notvalid"}}, + }, + expected: func() *Subgraph { + s, eventNode, _ := baseSubgraph(ids["a"], ids["b"]) + tagNode := NewTagNode("p", "notvalid") + s.AddNode(tagNode) + s.AddRel(NewTaggedRel(eventNode, tagNode, nil)) + return s + }(), + }, } + expanders := GetDefaultExpanderRegistry() + for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - got := EventToSubgraph(tc.event) + got := EventToSubgraph(tc.event, expanders) assertSubgraphsEqual(t, tc.expected, got) }) } diff --git a/expanders.go b/expanders.go new file mode 100644 index 0000000..1a82ca1 --- /dev/null +++ b/expanders.go @@ -0,0 +1,88 @@ +package heartwood + +import ( + roots "git.wisehodl.dev/jay/go-roots/events" +) + +type Expander func(e roots.Event, s *Subgraph) +type ExpanderRegistry []Expander + +func NewExpanderRegistry() ExpanderRegistry { + return []Expander{} +} + +func GetDefaultExpanderRegistry() ExpanderRegistry { + registry := NewExpanderRegistry() + + registry.Add(ExpandTaggedEvents) + registry.Add(ExpandTaggedUsers) + + return registry +} + +func (r *ExpanderRegistry) Add(m Expander) { + *r = append(*r, m) +} + +// Default Expander Functions + +func ExpandTaggedEvents(e roots.Event, s *Subgraph) { + tagNodes := s.NodesByLabel("Tag") + for _, tag := range e.Tags { + if !isValidTag(tag) { + continue + } + name := tag[0] + value := tag[1] + + if name != "e" || !roots.Hex64Pattern.MatchString(value) { + continue + } + + tagNode := findTagNode(tagNodes, name, value) + if tagNode == nil { + continue + } + + referencedEvent := NewEventNode(value) + + s.AddNode(referencedEvent) + s.AddRel(NewReferencesEventRel(tagNode, referencedEvent, nil)) + } +} + +func ExpandTaggedUsers(e roots.Event, s *Subgraph) { + tagNodes := s.NodesByLabel("Tag") + for _, tag := range e.Tags { + if !isValidTag(tag) { + continue + } + name := tag[0] + value := tag[1] + + if name != "p" || !roots.Hex64Pattern.MatchString(value) { + continue + } + + tagNode := findTagNode(tagNodes, name, value) + if tagNode == nil { + continue + } + + referencedEvent := NewUserNode(value) + + s.AddNode(referencedEvent) + s.AddRel(NewReferencesUserRel(tagNode, referencedEvent, nil)) + } +} + +// Helpers + +func findTagNode(nodes []*Node, name, value string) *Node { + for _, node := range nodes { + if node.Props["name"] == name && node.Props["value"] == value { + return node + } + } + return nil +} diff --git a/graph.go b/graph.go index ebc5bec..70f1a1b 100644 --- a/graph.go +++ b/graph.go @@ -199,6 +199,16 @@ func (s *Subgraph) Rels() []*Relationship { return s.rels } +func (s *Subgraph) NodesByLabel(label string) []*Node { + nodes := []*Node{} + for _, node := range s.nodes { + if node.Labels.Contains(label) { + nodes = append(nodes, node) + } + } + return nodes +} + // ======================================== // Structured Subgraph // ========================================