query.mx raw

   1  // Package graph provides types for NIP graph query extensions.
   2  // A graph query is a filter extension that traverses social graph edges.
   3  package graph
   4  
   5  import (
   6  	"fmt"
   7  )
   8  
   9  // Edge types for graph traversal.
  10  const (
  11  	EdgePubkeyPubkey = "pp" // noun-noun: direct social graph (ppg/gpp index)
  12  	EdgePubkeyEvent  = "pe" // adverb: authorship and p-tag references (peg/epg)
  13  	EdgeEventEvent   = "ee" // adjective: e-tag thread structure (eeg/gee)
  14  )
  15  
  16  // Direction constants.
  17  const (
  18  	DirOut  = "out"
  19  	DirIn   = "in"
  20  	DirBoth = "both"
  21  )
  22  
  23  // Query represents a graph traversal request.
  24  type Query struct {
  25  	Pubkey    []byte // 64-char hex seed node
  26  	Depth     int    // 1-16
  27  	Edge      string // "pp", "pe", "ee"
  28  	Direction string // "out", "in", "both"
  29  }
  30  
  31  // Validate checks all fields for correctness.
  32  func (q *Query) Validate() error {
  33  	if len(q.Pubkey) != 64 {
  34  		return fmt.Errorf("graph: pubkey must be 64 hex chars, got %d", len(q.Pubkey))
  35  	}
  36  	for _, c := range q.Pubkey {
  37  		if !isHex(c) {
  38  			return fmt.Errorf("graph: pubkey contains non-hex char %q", c)
  39  		}
  40  	}
  41  	if q.Depth < 1 || q.Depth > 16 {
  42  		return fmt.Errorf("graph: depth must be 1-16, got %d", q.Depth)
  43  	}
  44  	switch q.Edge {
  45  	case EdgePubkeyPubkey, EdgePubkeyEvent, EdgeEventEvent:
  46  	default:
  47  		return fmt.Errorf("graph: unknown edge type %q", q.Edge)
  48  	}
  49  	switch q.Direction {
  50  	case DirOut, DirIn, DirBoth:
  51  	default:
  52  		return fmt.Errorf("graph: unknown direction %q", q.Direction)
  53  	}
  54  	return nil
  55  }
  56  
  57  func isHex(b byte) bool {
  58  	return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F')
  59  }
  60  
  61  // IsPubkeyPubkey returns true for pp edge queries.
  62  func (q *Query) IsPubkeyPubkey() bool { return q.Edge == EdgePubkeyPubkey }
  63  
  64  // IsPubkeyEvent returns true for pe edge queries.
  65  func (q *Query) IsPubkeyEvent() bool { return q.Edge == EdgePubkeyEvent }
  66  
  67  // IsEventEvent returns true for ee edge queries.
  68  func (q *Query) IsEventEvent() bool { return q.Edge == EdgeEventEvent }
  69  
  70  // IsOutbound returns true for outbound-only traversal.
  71  func (q *Query) IsOutbound() bool { return q.Direction == DirOut }
  72  
  73  // IsInbound returns true for inbound-only traversal.
  74  func (q *Query) IsInbound() bool { return q.Direction == DirIn }
  75  
  76  // IsBidirectional returns true for both-direction traversal.
  77  func (q *Query) IsBidirectional() bool { return q.Direction == DirBoth }
  78  
  79  // CostFactor returns the exponential cost multiplier for rate limiting.
  80  // Bidirectional queries cost 1.5x more.
  81  func (q *Query) CostFactor(depthFactor float64) float64 {
  82  	cost := 1.0
  83  	for i := 0; i < q.Depth; i++ {
  84  		cost *= depthFactor
  85  	}
  86  	if q.IsBidirectional() {
  87  		cost *= 1.5
  88  	}
  89  	return cost
  90  }
  91