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