appview: add basic issue indexer #9

open
opened by boltless.me targeting master from feat/search
+1
.gitignore
··· 14 14 .DS_Store 15 15 .env 16 16 *.rdb 17 + *.bleve
+20
appview/indexer/base36/base36.go
··· 1 + // mostly copied from gitea/modules/indexer/internal/base32 2 + 3 + package base36 4 + 5 + import ( 6 + "fmt" 7 + "strconv" 8 + ) 9 + 10 + func Encode(i int64) string { 11 + return strconv.FormatInt(i, 36) 12 + } 13 + 14 + func Decode(s string) (int64, error) { 15 + i, err := strconv.ParseInt(s, 36, 64) 16 + if err != nil { 17 + return 0, fmt.Errorf("invalid base36 integer %q: %w", s, err) 18 + } 19 + return i, nil 20 + }
+58
appview/indexer/bleve/batch.go
··· 1 + // Copyright 2021 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package bleveutil 5 + 6 + import ( 7 + "github.com/blevesearch/bleve/v2" 8 + ) 9 + 10 + // FlushingBatch is a batch of operations that automatically flushes to the 11 + // underlying index once it reaches a certain size. 12 + type FlushingBatch struct { 13 + maxBatchSize int 14 + batch *bleve.Batch 15 + index bleve.Index 16 + } 17 + 18 + // NewFlushingBatch creates a new flushing batch for the specified index. Once 19 + // the number of operations in the batch reaches the specified limit, the batch 20 + // automatically flushes its operations to the index. 21 + func NewFlushingBatch(index bleve.Index, maxBatchSize int) *FlushingBatch { 22 + return &FlushingBatch{ 23 + maxBatchSize: maxBatchSize, 24 + batch: index.NewBatch(), 25 + index: index, 26 + } 27 + } 28 + 29 + // Index add a new index to batch 30 + func (b *FlushingBatch) Index(id string, data any) error { 31 + if err := b.batch.Index(id, data); err != nil { 32 + return err 33 + } 34 + return b.flushIfFull() 35 + } 36 + 37 + // Delete add a delete index to batch 38 + func (b *FlushingBatch) Delete(id string) error { 39 + b.batch.Delete(id) 40 + return b.flushIfFull() 41 + } 42 + 43 + func (b *FlushingBatch) flushIfFull() error { 44 + if b.batch.Size() < b.maxBatchSize { 45 + return nil 46 + } 47 + return b.Flush() 48 + } 49 + 50 + // Flush submit the batch and create a new one 51 + func (b *FlushingBatch) Flush() error { 52 + err := b.index.Batch(b.batch) 53 + if err != nil { 54 + return err 55 + } 56 + b.batch = b.index.NewBatch() 57 + return nil 58 + }
+24
appview/indexer/indexer.go
··· 1 + package indexer 2 + 3 + import ( 4 + "context" 5 + 6 + "tangled.sh/tangled.sh/core/appview/db" 7 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 8 + ) 9 + 10 + type Indexer struct { 11 + Issues *issues_indexer.Indexer 12 + } 13 + 14 + func New() *Indexer { 15 + return &Indexer { 16 + issues_indexer.NewIndexer("indexes.bleve"), 17 + } 18 + } 19 + 20 + // Init initializes all indexers 21 + func (ix *Indexer) Init(ctx context.Context, db *db.DB) error { 22 + ix.Issues.Init(ctx, db) 23 + return nil 24 + }
+199
appview/indexer/issues/indexer.go
··· 1 + package issues_indexer 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "log" 7 + "os" 8 + 9 + "github.com/blevesearch/bleve/v2" 10 + "github.com/blevesearch/bleve/v2/index/upsidedown" 11 + "github.com/blevesearch/bleve/v2/search/query" 12 + "tangled.sh/tangled.sh/core/appview/db" 13 + "tangled.sh/tangled.sh/core/appview/indexer/base36" 14 + bleveutil "tangled.sh/tangled.sh/core/appview/indexer/bleve" 15 + "tangled.sh/tangled.sh/core/appview/pagination" 16 + ) 17 + 18 + type Indexer struct { 19 + indexer bleve.Index 20 + path string 21 + } 22 + 23 + func NewIndexer(indexDir string) *Indexer { 24 + return &Indexer{ 25 + path: indexDir, 26 + } 27 + } 28 + 29 + // Init initializes the indexer 30 + func (ix *Indexer) Init(ctx context.Context, e db.Execer) { 31 + existed, err := ix.intialize(ctx) 32 + if err != nil { 33 + log.Fatalf("failed to initialize issue indexer: %v", err) 34 + } 35 + if !existed { 36 + log.Println("Populating the issue indexer") 37 + err := PopulateIndexer(ctx, ix, e) 38 + if err != nil { 39 + log.Fatalf("failed to populate issue indexer: %v", err) 40 + } 41 + } 42 + log.Println("Initialized the issue indexer") 43 + } 44 + 45 + func (ix *Indexer) intialize(_ context.Context) (bool, error) { 46 + if ix.indexer != nil { 47 + return false, errors.New("indexer is already initialized") 48 + } 49 + 50 + indexer, err := openIndexer(ix.path) 51 + if err != nil { 52 + return false, nil 53 + } 54 + if indexer != nil { 55 + ix.indexer = indexer 56 + return true, nil 57 + } 58 + 59 + mapping := bleve.NewIndexMapping() 60 + indexer, err = bleve.New(ix.path, mapping) 61 + if err != nil { 62 + return false, err 63 + } 64 + 65 + ix.indexer = indexer 66 + 67 + return false, nil 68 + } 69 + 70 + func openIndexer(path string) (bleve.Index, error) { 71 + _, err := os.Stat(path) 72 + indexer, err := bleve.Open(path) 73 + if err != nil { 74 + if errors.Is(err, upsidedown.IncompatibleVersion) { 75 + log.Println("Indexer was built with a previous version of bleve, deleting and rebuilding") 76 + return nil, os.RemoveAll(path) 77 + } 78 + return nil, nil 79 + } 80 + return indexer, nil 81 + } 82 + 83 + func PopulateIndexer(ctx context.Context, ix *Indexer, e db.Execer) error { 84 + repos, err := db.GetAllRepos(e, 10) 85 + if err != nil { 86 + return err 87 + } 88 + for _, repo := range repos { 89 + page := pagination.FirstPage() 90 + issues, err := db.GetIssues(e, repo.RepoAt(), true, page) 91 + if err != nil { 92 + return err 93 + } 94 + for _, issue := range issues { 95 + issue, _, err := db.GetIssueWithComments(e, repo.RepoAt(), issue.IssueId) 96 + if err != nil { 97 + return err 98 + } 99 + data := &IssueData { 100 + ID: issue.ID, 101 + IssueID: issue.IssueId, 102 + Title: issue.Title, 103 + Body: issue.Body, 104 + IsOpen: issue.Open, 105 + } 106 + err = ix.Index(ctx, data) 107 + if err != nil { 108 + return err 109 + } 110 + } 111 + } 112 + return nil 113 + } 114 + 115 + // IssueData data stored and will be indexed 116 + type IssueData struct { 117 + ID int64 `json:"id"` 118 + IssueID int `json:"issue_id"` 119 + Title string `json:"title"` 120 + Body string `json:"body"` 121 + 122 + IsOpen bool `json:"is_open"` 123 + Comments []IssueCommentData `json:"comments"` 124 + } 125 + 126 + type IssueCommentData struct { 127 + Body string `json:"body"` 128 + } 129 + 130 + type SearchOptions struct { 131 + Keyword string 132 + IsOpen *bool 133 + 134 + Page pagination.Page 135 + } 136 + 137 + type SearchResult struct { 138 + Hits []int64 139 + Total uint64 140 + } 141 + 142 + const maxBatchSize = 16 143 + 144 + func (ix *Indexer) Index(ctx context.Context, issues ...*IssueData) error { 145 + batch := bleveutil.NewFlushingBatch(ix.indexer, maxBatchSize) 146 + for _, issue := range issues { 147 + if err := batch.Index(base36.Encode(issue.ID), issue); err != nil { 148 + return err 149 + } 150 + } 151 + return batch.Flush() 152 + } 153 + 154 + // Search searches for issues 155 + func (ix *Indexer) Search(ctx context.Context, opts SearchOptions) (*SearchResult, error) { 156 + var queries []query.Query 157 + 158 + if opts.Keyword != "" { 159 + queries = append(queries, bleve.NewDisjunctionQuery( 160 + matchAndQuery(opts.Keyword, "title"), 161 + matchAndQuery(opts.Keyword, "body"), 162 + )) 163 + } 164 + if opts.IsOpen != nil { 165 + queries = append(queries, boolFieldQuery(*opts.IsOpen, "is_open")) 166 + } 167 + // TODO: append more queries 168 + 169 + var indexerQuery query.Query = bleve.NewConjunctionQuery(queries...) 170 + searchReq := bleve.NewSearchRequestOptions(indexerQuery, opts.Page.Limit, opts.Page.Offset, false) 171 + res, err := ix.indexer.SearchInContext(ctx, searchReq) 172 + if err != nil { 173 + return nil, nil 174 + } 175 + ret := &SearchResult{ 176 + Total: res.Total, 177 + Hits: make([]int64, len(res.Hits)), 178 + } 179 + for i, hit := range res.Hits { 180 + id, err := base36.Decode(hit.ID) 181 + if err != nil { 182 + return nil, err 183 + } 184 + ret.Hits[i] = id 185 + } 186 + return ret, nil 187 + } 188 + 189 + func matchAndQuery(keyword, field string) query.Query { 190 + q := bleve.NewMatchQuery(keyword) 191 + q.FieldVal = field 192 + return q 193 + } 194 + 195 + func boolFieldQuery(val bool, field string) query.Query { 196 + q := bleve.NewBoolFieldQuery(val) 197 + q.FieldVal = field 198 + return q 199 + }
+30
appview/indexer/notifier.go
··· 1 + package indexer 2 + 3 + import ( 4 + "context" 5 + 6 + "tangled.sh/tangled.sh/core/appview/db" 7 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 8 + "tangled.sh/tangled.sh/core/appview/notify" 9 + ) 10 + 11 + var _ notify.Notifier = &Indexer{} 12 + 13 + func (ix *Indexer) NewIssue(ctx context.Context, issue *db.Issue) { 14 + ix.Issues.Index(ctx, &issues_indexer.IssueData{ 15 + ID: issue.ID, 16 + IssueID: issue.IssueId, 17 + Title: issue.Title, 18 + Body: issue.Body, 19 + IsOpen: issue.Open, 20 + Comments: []issues_indexer.IssueCommentData{}, 21 + }) 22 + } 23 + 24 + func (ix *Indexer) NewIssueComment(ctx context.Context, comment db.Comment) { 25 + panic("unimplemented") 26 + } 27 + 28 + func (ix *Indexer) NewPullComment(ctx context.Context, comment db.PullComment) { 29 + panic("unimplemented") 30 + }
+10
appview/issues/issues.go
··· 20 20 "tangled.sh/tangled.sh/core/appview/config" 21 21 "tangled.sh/tangled.sh/core/appview/db" 22 22 "tangled.sh/tangled.sh/core/appview/idresolver" 23 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 24 + "tangled.sh/tangled.sh/core/appview/notify" 23 25 "tangled.sh/tangled.sh/core/appview/oauth" 24 26 "tangled.sh/tangled.sh/core/appview/pages" 25 27 "tangled.sh/tangled.sh/core/appview/pagination" ··· 34 36 db *db.DB 35 37 config *config.Config 36 38 posthog posthog.Client 39 + notifier notify.Notifier 40 + indexer *issues_indexer.Indexer 37 41 } 38 42 39 43 func New( ··· 44 48 db *db.DB, 45 49 config *config.Config, 46 50 posthog posthog.Client, 51 + notifier notify.Notifier, 52 + indexer *issues_indexer.Indexer, 47 53 ) *Issues { 48 54 return &Issues{ 49 55 oauth: oauth, ··· 53 59 db: db, 54 60 config: config, 55 61 posthog: posthog, 62 + notifier: notifier, 63 + indexer: indexer, 56 64 } 57 65 } 58 66 ··· 734 742 return 735 743 } 736 744 745 + rp.notifier.NewIssue(r.Context(), issue) 746 + 737 747 if !rp.config.Core.Dev { 738 748 err = rp.posthog.Enqueue(posthog.Capture{ 739 749 DistinctId: user.Did,
+1 -1
appview/notify/merged_notifier.go
··· 18 18 19 19 var _ Notifier = &mergedNotifier{} 20 20 21 - func (m *mergedNotifier) NewIssue(ctx context.Context, issue db.Issue) { 21 + func (m *mergedNotifier) NewIssue(ctx context.Context, issue *db.Issue) { 22 22 for _, notifier := range m.notifiers { 23 23 notifier.NewIssue(ctx, issue) 24 24 }
+1 -1
appview/notify/notifier.go
··· 7 7 ) 8 8 9 9 type Notifier interface { 10 - NewIssue(ctx context.Context, issue db.Issue) 10 + NewIssue(ctx context.Context, issue *db.Issue) 11 11 NewIssueComment(ctx context.Context, comment db.Comment) 12 12 13 13 NewPullComment(ctx context.Context, comment db.PullComment)
+1 -1
appview/state/router.go
··· 191 191 } 192 192 193 193 func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 194 - issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog, s.notifier) 194 + issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.posthog, s.notifier, s.indexer.Issues) 195 195 return issues.Router(mw) 196 196 197 197 }
+10
appview/state/state.go
··· 25 25 "tangled.sh/tangled.sh/core/appview/config" 26 26 "tangled.sh/tangled.sh/core/appview/db" 27 27 "tangled.sh/tangled.sh/core/appview/idresolver" 28 + "tangled.sh/tangled.sh/core/appview/indexer" 28 29 "tangled.sh/tangled.sh/core/appview/notify" 29 30 "tangled.sh/tangled.sh/core/appview/oauth" 30 31 "tangled.sh/tangled.sh/core/appview/pages" ··· 39 40 type State struct { 40 41 db *db.DB 41 42 notifier notify.Notifier 43 + indexer *indexer.Indexer 42 44 oauth *oauth.OAuth 43 45 enforcer *rbac.Enforcer 44 46 tidClock syntax.TIDClock ··· 59 61 return nil, fmt.Errorf("failed to create db: %w", err) 60 62 } 61 63 64 + indexer := indexer.New() 65 + err = indexer.Init(ctx, d) 66 + if err != nil { 67 + return nil, fmt.Errorf("failed to create indexer: %w", err) 68 + } 69 + 62 70 enforcer, err := rbac.NewEnforcer(config.Core.DbPath) 63 71 if err != nil { 64 72 return nil, fmt.Errorf("failed to create enforcer: %w", err) ··· 137 145 spindlestream.Start(ctx) 138 146 139 147 notifier := notify.NewMergedNotifier( 148 + indexer, 140 149 ) 141 150 142 151 state := &State{ 143 152 d, 144 153 notifier, 154 + indexer, 145 155 oauth, 146 156 enforcer, 147 157 clock,
+1 -1
flake.nix
··· 56 56 nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system}); 57 57 inherit (gitignore.lib) gitignoreSource; 58 58 mkPackageSet = pkgs: let 59 - goModHash = "sha256-SLi+nALwCd/Lzn3aljwPqCo2UaM9hl/4OAjcHQLt2Bk="; 59 + goModHash = "sha256-N+Rm2PanY+fVRE/6Gaz+y3vSUjqKOtngpkS4fn7VFR0="; 60 60 sqlite-lib = pkgs.callPackage ./nix/pkgs/sqlite-lib.nix { 61 61 inherit (pkgs) gcc; 62 62 inherit sqlite-lib-src;
+27
go.mod
··· 48 48 dario.cat/mergo v1.0.1 // indirect 49 49 github.com/Microsoft/go-winio v0.6.2 // indirect 50 50 github.com/ProtonMail/go-crypto v1.2.0 // indirect 51 + github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect 51 52 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 52 53 github.com/avast/retry-go/v4 v4.6.1 // indirect 53 54 github.com/aymerick/douceur v0.2.0 // indirect 54 55 github.com/beorn7/perks v1.0.1 // indirect 56 + github.com/bits-and-blooms/bitset v1.22.0 // indirect 57 + github.com/blevesearch/bleve/v2 v2.5.2 // indirect 58 + github.com/blevesearch/bleve_index_api v1.2.8 // indirect 59 + github.com/blevesearch/geo v0.2.3 // indirect 60 + github.com/blevesearch/go-faiss v1.0.25 // indirect 61 + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect 62 + github.com/blevesearch/gtreap v0.1.1 // indirect 63 + github.com/blevesearch/mmap-go v1.0.4 // indirect 64 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect 65 + github.com/blevesearch/segment v0.9.1 // indirect 66 + github.com/blevesearch/snowballstem v0.9.0 // indirect 67 + github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect 68 + github.com/blevesearch/vellum v1.1.0 // indirect 69 + github.com/blevesearch/zapx/v11 v11.4.2 // indirect 70 + github.com/blevesearch/zapx/v12 v12.4.2 // indirect 71 + github.com/blevesearch/zapx/v13 v13.4.2 // indirect 72 + github.com/blevesearch/zapx/v14 v14.4.2 // indirect 73 + github.com/blevesearch/zapx/v15 v15.4.2 // indirect 74 + github.com/blevesearch/zapx/v16 v16.2.4 // indirect 55 75 github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect 56 76 github.com/casbin/govaluate v1.3.0 // indirect 57 77 github.com/cespare/xxhash/v2 v2.3.0 // indirect ··· 78 98 github.com/gogo/protobuf v1.3.2 // indirect 79 99 github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 80 100 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 101 + github.com/golang/protobuf v1.5.4 // indirect 102 + github.com/golang/snappy v0.0.4 // indirect 81 103 github.com/gorilla/css v1.0.1 // indirect 82 104 github.com/gorilla/securecookie v1.1.2 // indirect 83 105 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect ··· 95 117 github.com/ipfs/go-log v1.0.5 // indirect 96 118 github.com/ipfs/go-log/v2 v2.6.0 // indirect 97 119 github.com/ipfs/go-metrics-interface v0.3.0 // indirect 120 + github.com/json-iterator/go v1.1.12 // indirect 98 121 github.com/kevinburke/ssh_config v1.2.0 // indirect 99 122 github.com/klauspost/compress v1.18.0 // indirect 100 123 github.com/klauspost/cpuid/v2 v2.2.10 // indirect ··· 108 131 github.com/moby/docker-image-spec v1.3.1 // indirect 109 132 github.com/moby/sys/atomicwriter v0.1.0 // indirect 110 133 github.com/moby/term v0.5.2 // indirect 134 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 135 + github.com/modern-go/reflect2 v1.0.2 // indirect 111 136 github.com/morikuni/aec v1.0.0 // indirect 112 137 github.com/mr-tron/base58 v1.2.0 // indirect 138 + github.com/mschoch/smat v0.2.0 // indirect 113 139 github.com/multiformats/go-base32 v0.1.0 // indirect 114 140 github.com/multiformats/go-base36 v0.2.0 // indirect 115 141 github.com/multiformats/go-multibase v0.2.0 // indirect ··· 135 161 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 136 162 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 137 163 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 164 + go.etcd.io/bbolt v1.4.0 // indirect 138 165 go.opentelemetry.io/auto/sdk v1.1.0 // indirect 139 166 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect 140 167 go.opentelemetry.io/otel v1.36.0 // indirect
+58
go.sum
··· 9 9 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 10 10 github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= 11 11 github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 12 + github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= 13 + github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= 12 14 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 13 15 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 14 16 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= ··· 23 25 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 24 26 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 25 27 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 28 + github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 29 + github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= 30 + github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 31 + github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8= 32 + github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo= 33 + github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y= 34 + github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= 35 + github.com/blevesearch/geo v0.2.3 h1:K9/vbGI9ehlXdxjxDRJtoAMt7zGAsMIzc6n8zWcwnhg= 36 + github.com/blevesearch/geo v0.2.3/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8= 37 + github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= 38 + github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= 39 + github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= 40 + github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= 41 + github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= 42 + github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= 43 + github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= 44 + github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= 45 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10 h1:Yqk0XD1mE0fDZAJXTjawJ8If/85JxnLd8v5vG/jWE/s= 46 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8= 47 + github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= 48 + github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= 49 + github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= 50 + github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= 51 + github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= 52 + github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= 53 + github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= 54 + github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= 55 + github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs= 56 + github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc= 57 + github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE= 58 + github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58= 59 + github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks= 60 + github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk= 61 + github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0= 62 + github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= 63 + github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= 64 + github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= 65 + github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww= 66 + github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs= 26 67 github.com/bluesky-social/indigo v0.0.0-20250520232546-236dd575c91e h1:DVD+HxQsDCVJtAkjfIKZVaBNc3kayHaU+A2TJZkFdp4= 27 68 github.com/bluesky-social/indigo v0.0.0-20250520232546-236dd575c91e/go.mod h1:ovyxp8AMO1Hoe838vMJUbqHTZaAR8ABM3g3TXu+A5Ng= 28 69 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA= ··· 143 184 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 144 185 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 145 186 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 187 + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 188 + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 189 + github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 190 + github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 146 191 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 147 192 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 148 193 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= ··· 151 196 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 152 197 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 153 198 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 199 + github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 154 200 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 155 201 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 156 202 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= ··· 220 266 github.com/ipfs/go-test v0.2.1/go.mod h1:dzu+KB9cmWjuJnXFDYJwC25T3j1GcN57byN+ixmK39M= 221 267 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 222 268 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 269 + github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 270 + github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 223 271 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 224 272 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 225 273 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= ··· 273 321 github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 274 322 github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 275 323 github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 324 + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 325 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 326 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 327 + github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 328 + github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 276 329 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 277 330 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 278 331 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 279 332 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 333 + github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= 334 + github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= 280 335 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 281 336 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 282 337 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= ··· 411 466 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 412 467 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 413 468 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 469 + go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= 470 + go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= 414 471 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 415 472 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 416 473 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= ··· 592 649 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 593 650 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 594 651 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 652 + gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 595 653 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 596 654 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 597 655 gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=