// Package graph provides types for NIP graph query extensions. // A graph query is a filter extension that traverses social graph edges. package graph import ( "fmt" ) // Edge types for graph traversal. const ( EdgePubkeyPubkey = "pp" // noun-noun: direct social graph (ppg/gpp index) EdgePubkeyEvent = "pe" // adverb: authorship and p-tag references (peg/epg) EdgeEventEvent = "ee" // adjective: e-tag thread structure (eeg/gee) ) // Direction constants. const ( DirOut = "out" DirIn = "in" DirBoth = "both" ) // Query represents a graph traversal request. type Query struct { Pubkey []byte // 64-char hex seed node Depth int // 1-16 Edge string // "pp", "pe", "ee" Direction string // "out", "in", "both" } // Validate checks all fields for correctness. func (q *Query) Validate() error { if len(q.Pubkey) != 64 { return fmt.Errorf("graph: pubkey must be 64 hex chars, got %d", len(q.Pubkey)) } for _, c := range q.Pubkey { if !isHex(c) { return fmt.Errorf("graph: pubkey contains non-hex char %q", c) } } if q.Depth < 1 || q.Depth > 16 { return fmt.Errorf("graph: depth must be 1-16, got %d", q.Depth) } switch q.Edge { case EdgePubkeyPubkey, EdgePubkeyEvent, EdgeEventEvent: default: return fmt.Errorf("graph: unknown edge type %q", q.Edge) } switch q.Direction { case DirOut, DirIn, DirBoth: default: return fmt.Errorf("graph: unknown direction %q", q.Direction) } return nil } func isHex(b byte) bool { return (b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F') } // IsPubkeyPubkey returns true for pp edge queries. func (q *Query) IsPubkeyPubkey() bool { return q.Edge == EdgePubkeyPubkey } // IsPubkeyEvent returns true for pe edge queries. func (q *Query) IsPubkeyEvent() bool { return q.Edge == EdgePubkeyEvent } // IsEventEvent returns true for ee edge queries. func (q *Query) IsEventEvent() bool { return q.Edge == EdgeEventEvent } // IsOutbound returns true for outbound-only traversal. func (q *Query) IsOutbound() bool { return q.Direction == DirOut } // IsInbound returns true for inbound-only traversal. func (q *Query) IsInbound() bool { return q.Direction == DirIn } // IsBidirectional returns true for both-direction traversal. func (q *Query) IsBidirectional() bool { return q.Direction == DirBoth } // CostFactor returns the exponential cost multiplier for rate limiting. // Bidirectional queries cost 1.5x more. func (q *Query) CostFactor(depthFactor float64) float64 { cost := 1.0 for i := 0; i < q.Depth; i++ { cost *= depthFactor } if q.IsBidirectional() { cost *= 1.5 } return cost }