graph-result.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "sort"
7 )
8
9 // GraphResult contains depth-organized traversal results for graph queries.
10 // It tracks pubkeys and events discovered at each depth level, ensuring
11 // each entity appears only at the depth where it was first discovered.
12 type GraphResult struct {
13 // PubkeysByDepth maps depth -> pubkeys first discovered at that depth.
14 // Each pubkey appears ONLY in the array for the depth where it was first seen.
15 // Depth 1 = direct connections, Depth 2 = connections of connections, etc.
16 PubkeysByDepth map[int][]string
17
18 // EventsByDepth maps depth -> event IDs discovered at that depth.
19 // Used for thread traversal queries.
20 EventsByDepth map[int][]string
21
22 // FirstSeenPubkey tracks which depth each pubkey was first discovered.
23 // Key is pubkey hex, value is the depth (1-indexed).
24 FirstSeenPubkey map[string]int
25
26 // FirstSeenEvent tracks which depth each event was first discovered.
27 // Key is event ID hex, value is the depth (1-indexed).
28 FirstSeenEvent map[string]int
29
30 // TotalPubkeys is the count of unique pubkeys discovered across all depths.
31 TotalPubkeys int
32
33 // TotalEvents is the count of unique events discovered across all depths.
34 TotalEvents int
35
36 // InboundRefs tracks inbound references (events that reference discovered items).
37 // Structure: kind -> target_id -> []referencing_event_ids
38 InboundRefs map[uint16]map[string][]string
39
40 // OutboundRefs tracks outbound references (events referenced by discovered items).
41 // Structure: kind -> source_id -> []referenced_event_ids
42 OutboundRefs map[uint16]map[string][]string
43 }
44
45 // NewGraphResult creates a new initialized GraphResult.
46 func NewGraphResult() *GraphResult {
47 return &GraphResult{
48 PubkeysByDepth: make(map[int][]string),
49 EventsByDepth: make(map[int][]string),
50 FirstSeenPubkey: make(map[string]int),
51 FirstSeenEvent: make(map[string]int),
52 InboundRefs: make(map[uint16]map[string][]string),
53 OutboundRefs: make(map[uint16]map[string][]string),
54 }
55 }
56
57 // AddPubkeyAtDepth adds a pubkey to the result at the specified depth if not already seen.
58 // Returns true if the pubkey was added (first time seen), false if already exists.
59 func (r *GraphResult) AddPubkeyAtDepth(pubkeyHex string, depth int) bool {
60 if _, exists := r.FirstSeenPubkey[pubkeyHex]; exists {
61 return false
62 }
63
64 r.FirstSeenPubkey[pubkeyHex] = depth
65 r.PubkeysByDepth[depth] = append(r.PubkeysByDepth[depth], pubkeyHex)
66 r.TotalPubkeys++
67 return true
68 }
69
70 // AddEventAtDepth adds an event ID to the result at the specified depth if not already seen.
71 // Returns true if the event was added (first time seen), false if already exists.
72 func (r *GraphResult) AddEventAtDepth(eventIDHex string, depth int) bool {
73 if _, exists := r.FirstSeenEvent[eventIDHex]; exists {
74 return false
75 }
76
77 r.FirstSeenEvent[eventIDHex] = depth
78 r.EventsByDepth[depth] = append(r.EventsByDepth[depth], eventIDHex)
79 r.TotalEvents++
80 return true
81 }
82
83 // HasPubkey returns true if the pubkey has been discovered at any depth.
84 func (r *GraphResult) HasPubkey(pubkeyHex string) bool {
85 _, exists := r.FirstSeenPubkey[pubkeyHex]
86 return exists
87 }
88
89 // HasEvent returns true if the event has been discovered at any depth.
90 func (r *GraphResult) HasEvent(eventIDHex string) bool {
91 _, exists := r.FirstSeenEvent[eventIDHex]
92 return exists
93 }
94
95 // GetPubkeyDepth returns the depth at which a pubkey was first discovered.
96 // Returns 0 if the pubkey was not found.
97 func (r *GraphResult) GetPubkeyDepth(pubkeyHex string) int {
98 return r.FirstSeenPubkey[pubkeyHex]
99 }
100
101 // GetEventDepth returns the depth at which an event was first discovered.
102 // Returns 0 if the event was not found.
103 func (r *GraphResult) GetEventDepth(eventIDHex string) int {
104 return r.FirstSeenEvent[eventIDHex]
105 }
106
107 // GetDepthsSorted returns all depths that have pubkeys, sorted ascending.
108 func (r *GraphResult) GetDepthsSorted() []int {
109 depths := make([]int, 0, len(r.PubkeysByDepth))
110 for d := range r.PubkeysByDepth {
111 depths = append(depths, d)
112 }
113 sort.Ints(depths)
114 return depths
115 }
116
117 // GetEventDepthsSorted returns all depths that have events, sorted ascending.
118 func (r *GraphResult) GetEventDepthsSorted() []int {
119 depths := make([]int, 0, len(r.EventsByDepth))
120 for d := range r.EventsByDepth {
121 depths = append(depths, d)
122 }
123 sort.Ints(depths)
124 return depths
125 }
126
127 // ToDepthArrays converts the result to the response format: array of arrays.
128 // Index 0 = depth 1 pubkeys, Index 1 = depth 2 pubkeys, etc.
129 // Empty arrays are included for depths with no pubkeys to maintain index alignment.
130 func (r *GraphResult) ToDepthArrays() [][]string {
131 if len(r.PubkeysByDepth) == 0 {
132 return [][]string{}
133 }
134
135 // Find the maximum depth
136 maxDepth := 0
137 for d := range r.PubkeysByDepth {
138 if d > maxDepth {
139 maxDepth = d
140 }
141 }
142
143 // Create result array with entries for each depth
144 result := make([][]string, maxDepth)
145 for i := 0; i < maxDepth; i++ {
146 depth := i + 1 // depths are 1-indexed
147 if pubkeys, exists := r.PubkeysByDepth[depth]; exists {
148 result[i] = pubkeys
149 } else {
150 result[i] = []string{} // Empty array for depths with no pubkeys
151 }
152 }
153 return result
154 }
155
156 // ToEventDepthArrays converts event results to the response format: array of arrays.
157 // Index 0 = depth 1 events, Index 1 = depth 2 events, etc.
158 func (r *GraphResult) ToEventDepthArrays() [][]string {
159 if len(r.EventsByDepth) == 0 {
160 return [][]string{}
161 }
162
163 maxDepth := 0
164 for d := range r.EventsByDepth {
165 if d > maxDepth {
166 maxDepth = d
167 }
168 }
169
170 result := make([][]string, maxDepth)
171 for i := 0; i < maxDepth; i++ {
172 depth := i + 1
173 if events, exists := r.EventsByDepth[depth]; exists {
174 result[i] = events
175 } else {
176 result[i] = []string{}
177 }
178 }
179 return result
180 }
181
182 // AddInboundRef records an inbound reference from a referencing event to a target.
183 func (r *GraphResult) AddInboundRef(kind uint16, targetIDHex string, referencingEventIDHex string) {
184 if r.InboundRefs[kind] == nil {
185 r.InboundRefs[kind] = make(map[string][]string)
186 }
187 r.InboundRefs[kind][targetIDHex] = append(r.InboundRefs[kind][targetIDHex], referencingEventIDHex)
188 }
189
190 // AddOutboundRef records an outbound reference from a source event to a referenced event.
191 func (r *GraphResult) AddOutboundRef(kind uint16, sourceIDHex string, referencedEventIDHex string) {
192 if r.OutboundRefs[kind] == nil {
193 r.OutboundRefs[kind] = make(map[string][]string)
194 }
195 r.OutboundRefs[kind][sourceIDHex] = append(r.OutboundRefs[kind][sourceIDHex], referencedEventIDHex)
196 }
197
198 // RefAggregation represents aggregated reference data for a single target/source.
199 type RefAggregation struct {
200 // TargetEventID is the event ID being referenced (for inbound) or referencing (for outbound)
201 TargetEventID string
202
203 // TargetAuthor is the author pubkey of the target event (if known)
204 TargetAuthor string
205
206 // TargetDepth is the depth at which this target was discovered in the graph
207 TargetDepth int
208
209 // RefKind is the kind of the referencing events
210 RefKind uint16
211
212 // RefCount is the number of references to/from this target
213 RefCount int
214
215 // RefEventIDs is the list of event IDs that reference this target
216 RefEventIDs []string
217 }
218
219 // GetInboundRefsSorted returns inbound refs for a kind, sorted by count descending.
220 func (r *GraphResult) GetInboundRefsSorted(kind uint16) []RefAggregation {
221 kindRefs := r.InboundRefs[kind]
222 if kindRefs == nil {
223 return nil
224 }
225
226 aggs := make([]RefAggregation, 0, len(kindRefs))
227 for targetID, refs := range kindRefs {
228 agg := RefAggregation{
229 TargetEventID: targetID,
230 TargetDepth: r.GetEventDepth(targetID),
231 RefKind: kind,
232 RefCount: len(refs),
233 RefEventIDs: refs,
234 }
235 aggs = append(aggs, agg)
236 }
237
238 // Sort by count descending
239 sort.Slice(aggs, func(i, j int) bool {
240 return aggs[i].RefCount > aggs[j].RefCount
241 })
242
243 return aggs
244 }
245
246 // GetOutboundRefsSorted returns outbound refs for a kind, sorted by count descending.
247 func (r *GraphResult) GetOutboundRefsSorted(kind uint16) []RefAggregation {
248 kindRefs := r.OutboundRefs[kind]
249 if kindRefs == nil {
250 return nil
251 }
252
253 aggs := make([]RefAggregation, 0, len(kindRefs))
254 for sourceID, refs := range kindRefs {
255 agg := RefAggregation{
256 TargetEventID: sourceID,
257 TargetDepth: r.GetEventDepth(sourceID),
258 RefKind: kind,
259 RefCount: len(refs),
260 RefEventIDs: refs,
261 }
262 aggs = append(aggs, agg)
263 }
264
265 sort.Slice(aggs, func(i, j int) bool {
266 return aggs[i].RefCount > aggs[j].RefCount
267 })
268
269 return aggs
270 }
271
272 // GetAllPubkeys returns all pubkeys discovered across all depths.
273 func (r *GraphResult) GetAllPubkeys() []string {
274 all := make([]string, 0, r.TotalPubkeys)
275 for _, pubkeys := range r.PubkeysByDepth {
276 all = append(all, pubkeys...)
277 }
278 return all
279 }
280
281 // GetAllEvents returns all event IDs discovered across all depths.
282 func (r *GraphResult) GetAllEvents() []string {
283 all := make([]string, 0, r.TotalEvents)
284 for _, events := range r.EventsByDepth {
285 all = append(all, events...)
286 }
287 return all
288 }
289
290 // GetPubkeysAtDepth returns pubkeys at a specific depth, or empty slice if none.
291 func (r *GraphResult) GetPubkeysAtDepth(depth int) []string {
292 if pubkeys, exists := r.PubkeysByDepth[depth]; exists {
293 return pubkeys
294 }
295 return []string{}
296 }
297
298 // GetEventsAtDepth returns events at a specific depth, or empty slice if none.
299 func (r *GraphResult) GetEventsAtDepth(depth int) []string {
300 if events, exists := r.EventsByDepth[depth]; exists {
301 return events
302 }
303 return []string{}
304 }
305
306 // Interface methods for external package access (e.g., pkg/protocol/graph)
307 // These allow the graph executor to extract data without direct struct access.
308
309 // GetPubkeysByDepth returns the PubkeysByDepth map for external access.
310 func (r *GraphResult) GetPubkeysByDepth() map[int][]string {
311 return r.PubkeysByDepth
312 }
313
314 // GetEventsByDepth returns the EventsByDepth map for external access.
315 func (r *GraphResult) GetEventsByDepth() map[int][]string {
316 return r.EventsByDepth
317 }
318
319 // GetTotalPubkeys returns the total pubkey count for external access.
320 func (r *GraphResult) GetTotalPubkeys() int {
321 return r.TotalPubkeys
322 }
323
324 // GetTotalEvents returns the total event count for external access.
325 func (r *GraphResult) GetTotalEvents() int {
326 return r.TotalEvents
327 }
328
329 // GetInboundRefs returns the InboundRefs map for external access.
330 func (r *GraphResult) GetInboundRefs() map[uint16]map[string][]string {
331 return r.InboundRefs
332 }
333
334 // GetOutboundRefs returns the OutboundRefs map for external access.
335 func (r *GraphResult) GetOutboundRefs() map[uint16]map[string][]string {
336 return r.OutboundRefs
337 }
338