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