appview: add basic issue indexer (wip) #494

merged
opened by boltless.me targeting master from boltless.me/core: feat/search
  • Heavily inspired by gitea
  • add GetAllIssues which only receives a paginator and gathers all issues ignoring repoAt field

Signed-off-by: Seongmin Lee boltlessengineer@proton.me

Changed files
+504 -1
appview
db
indexer
issues
models
pages
state
+1
.gitignore
···
.env
*.rdb
.envrc
+
*.bleve
# Created if following hacking.md
genjwks.out
/nix/vm-data
+56
appview/db/issues.go
···
return ownerDid, err
}
+
func GetAllIssues(e Execer, page pagination.Page) ([]Issue, error) {
+
var issues []Issue
+
rows, err := e.Query(
+
`
+
select
+
i.id,
+
i.owner_did,
+
i.repo_at,
+
i.issue_id,
+
i.created,
+
i.title,
+
i.body,
+
i.open,
+
count(c.id) as comment_count
+
from
+
issues i
+
left join
+
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
+
group by
+
i.id
+
order by i.created desc
+
limit ? offset ?`,
+
page.Limit,
+
page.Offset,
+
)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var issue Issue
+
var createdAt string
+
var metadata IssueMetadata
+
err := rows.Scan(&issue.ID, &issue.OwnerDid, &issue.RepoAt, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
+
if err != nil {
+
return nil, err
+
}
+
+
createdTime, err := time.Parse(time.RFC3339, createdAt)
+
if err != nil {
+
return nil, err
+
}
+
issue.Created = createdTime
+
issue.Metadata = &metadata
+
+
issues = append(issues, issue)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return issues, nil
+
}
+
func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {
var issues []Issue
openValue := 0
+20
appview/indexer/base36/base36.go
···
+
// mostly copied from gitea/modules/indexer/internal/base32
+
+
package base36
+
+
import (
+
"fmt"
+
"strconv"
+
)
+
+
func Encode(i int64) string {
+
return strconv.FormatInt(i, 36)
+
}
+
+
func Decode(s string) (int64, error) {
+
i, err := strconv.ParseInt(s, 36, 64)
+
if err != nil {
+
return 0, fmt.Errorf("invalid base36 integer %q: %w", s, err)
+
}
+
return i, nil
+
}
+58
appview/indexer/bleve/batch.go
···
+
// Copyright 2021 The Gitea Authors. All rights reserved.
+
// SPDX-License-Identifier: MIT
+
+
package bleveutil
+
+
import (
+
"github.com/blevesearch/bleve/v2"
+
)
+
+
// FlushingBatch is a batch of operations that automatically flushes to the
+
// underlying index once it reaches a certain size.
+
type FlushingBatch struct {
+
maxBatchSize int
+
batch *bleve.Batch
+
index bleve.Index
+
}
+
+
// NewFlushingBatch creates a new flushing batch for the specified index. Once
+
// the number of operations in the batch reaches the specified limit, the batch
+
// automatically flushes its operations to the index.
+
func NewFlushingBatch(index bleve.Index, maxBatchSize int) *FlushingBatch {
+
return &FlushingBatch{
+
maxBatchSize: maxBatchSize,
+
batch: index.NewBatch(),
+
index: index,
+
}
+
}
+
+
// Index add a new index to batch
+
func (b *FlushingBatch) Index(id string, data any) error {
+
if err := b.batch.Index(id, data); err != nil {
+
return err
+
}
+
return b.flushIfFull()
+
}
+
+
// Delete add a delete index to batch
+
func (b *FlushingBatch) Delete(id string) error {
+
b.batch.Delete(id)
+
return b.flushIfFull()
+
}
+
+
func (b *FlushingBatch) flushIfFull() error {
+
if b.batch.Size() < b.maxBatchSize {
+
return nil
+
}
+
return b.Flush()
+
}
+
+
// Flush submit the batch and create a new one
+
func (b *FlushingBatch) Flush() error {
+
err := b.index.Batch(b.batch)
+
if err != nil {
+
return err
+
}
+
b.batch = b.index.NewBatch()
+
return nil
+
}
+27
appview/indexer/indexer.go
···
+
package indexer
+
+
import (
+
"context"
+
+
"tangled.sh/tangled.sh/core/appview/db"
+
issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues"
+
"tangled.sh/tangled.sh/core/appview/notify"
+
)
+
+
type Indexer struct {
+
Issues *issues_indexer.Indexer
+
notify.BaseNotifier
+
}
+
+
func New() *Indexer {
+
return &Indexer {
+
issues_indexer.NewIndexer("indexes.bleve"),
+
notify.BaseNotifier{},
+
}
+
}
+
+
// Init initializes all indexers
+
func (ix *Indexer) Init(ctx context.Context, db *db.DB) error {
+
ix.Issues.Init(ctx, db)
+
return nil
+
}
+192
appview/indexer/issues/indexer.go
···
+
package issues_indexer
+
+
import (
+
"context"
+
"errors"
+
"log"
+
"os"
+
+
"github.com/blevesearch/bleve/v2"
+
"github.com/blevesearch/bleve/v2/index/upsidedown"
+
"github.com/blevesearch/bleve/v2/search/query"
+
"tangled.sh/tangled.sh/core/appview/db"
+
"tangled.sh/tangled.sh/core/appview/indexer/base36"
+
"tangled.sh/tangled.sh/core/appview/indexer/bleve"
+
"tangled.sh/tangled.sh/core/appview/models"
+
"tangled.sh/tangled.sh/core/appview/pagination"
+
)
+
+
type Indexer struct {
+
indexer bleve.Index
+
path string
+
}
+
+
func NewIndexer(indexDir string) *Indexer {
+
return &Indexer{
+
path: indexDir,
+
}
+
}
+
+
// Init initializes the indexer
+
func (ix *Indexer) Init(ctx context.Context, e db.Execer) {
+
existed, err := ix.intialize(ctx)
+
if err != nil {
+
log.Fatalf("failed to initialize issue indexer: %v", err)
+
}
+
if !existed {
+
log.Println("Populating the issue indexer")
+
err := PopulateIndexer(ctx, ix, e)
+
if err != nil {
+
log.Fatalf("failed to populate issue indexer: %v", err)
+
}
+
}
+
log.Println("Initialized the issue indexer")
+
}
+
+
func (ix *Indexer) intialize(_ context.Context) (bool, error) {
+
if ix.indexer != nil {
+
return false, errors.New("indexer is already initialized")
+
}
+
+
indexer, err := openIndexer(ix.path)
+
if err != nil {
+
return false, nil
+
}
+
if indexer != nil {
+
ix.indexer = indexer
+
return true, nil
+
}
+
+
mapping := bleve.NewIndexMapping()
+
indexer, err = bleve.New(ix.path, mapping)
+
if err != nil {
+
return false, err
+
}
+
+
ix.indexer = indexer
+
+
return false, nil
+
}
+
+
func openIndexer(path string) (bleve.Index, error) {
+
_, err := os.Stat(path)
+
indexer, err := bleve.Open(path)
+
if err != nil {
+
if errors.Is(err, upsidedown.IncompatibleVersion) {
+
log.Println("Indexer was built with a previous version of bleve, deleting and rebuilding")
+
return nil, os.RemoveAll(path)
+
}
+
return nil, nil
+
}
+
return indexer, nil
+
}
+
+
func PopulateIndexer(ctx context.Context, ix *Indexer, e db.Execer) error {
+
page := pagination.FirstPage()
+
for {
+
issues, err := db.GetAllIssues(e, page)
+
if err != nil {
+
return err
+
}
+
var dataList []*IssueData
+
for _, issue := range issues {
+
issue, _, err := db.GetIssueWithComments(e, issue.RepoAt, issue.IssueId)
+
if err != nil {
+
return err
+
}
+
dataList = append(dataList, &IssueData{
+
ID: issue.ID,
+
IssueID: issue.IssueId,
+
Title: issue.Title,
+
Body: issue.Body,
+
IsOpen: issue.Open,
+
})
+
}
+
err = ix.Index(ctx, dataList...)
+
if err != nil {
+
return err
+
}
+
if len(issues) < page.Limit {
+
break
+
}
+
page = page.Next()
+
}
+
return nil
+
}
+
+
// IssueData data stored and will be indexed
+
type IssueData struct {
+
ID int64 `json:"id"`
+
IssueID int `json:"issue_id"`
+
Title string `json:"title"`
+
Body string `json:"body"`
+
+
IsOpen bool `json:"is_open"`
+
Comments []IssueCommentData `json:"comments"`
+
}
+
+
type IssueCommentData struct {
+
Body string `json:"body"`
+
}
+
+
type SearchResult struct {
+
Hits []int64
+
Total uint64
+
}
+
+
const maxBatchSize = 20
+
+
func (ix *Indexer) Index(ctx context.Context, issues ...*IssueData) error {
+
batch := bleveutil.NewFlushingBatch(ix.indexer, maxBatchSize)
+
for _, issue := range issues {
+
if err := batch.Index(base36.Encode(issue.ID), issue); err != nil {
+
return err
+
}
+
}
+
return batch.Flush()
+
}
+
+
// Search searches for issues
+
func (ix *Indexer) Search(ctx context.Context, opts models.IssueSearchOptions) (*SearchResult, error) {
+
var queries []query.Query
+
+
if opts.Keyword != "" {
+
queries = append(queries, bleve.NewDisjunctionQuery(
+
matchAndQuery(opts.Keyword, "title"),
+
matchAndQuery(opts.Keyword, "body"),
+
))
+
}
+
queries = append(queries, boolFieldQuery(opts.IsOpen, "is_open"))
+
// TODO: append more queries
+
+
var indexerQuery query.Query = bleve.NewConjunctionQuery(queries...)
+
searchReq := bleve.NewSearchRequestOptions(indexerQuery, opts.Page.Limit, opts.Page.Offset, false)
+
res, err := ix.indexer.SearchInContext(ctx, searchReq)
+
if err != nil {
+
return nil, nil
+
}
+
ret := &SearchResult{
+
Total: res.Total,
+
Hits: make([]int64, len(res.Hits)),
+
}
+
for i, hit := range res.Hits {
+
id, err := base36.Decode(hit.ID)
+
if err != nil {
+
return nil, err
+
}
+
ret.Hits[i] = id
+
}
+
return ret, nil
+
}
+
+
func matchAndQuery(keyword, field string) query.Query {
+
q := bleve.NewMatchQuery(keyword)
+
q.FieldVal = field
+
return q
+
}
+
+
func boolFieldQuery(val bool, field string) query.Query {
+
q := bleve.NewBoolFieldQuery(val)
+
q.FieldVal = field
+
return q
+
}
+26
appview/indexer/notifier.go
···
+
package indexer
+
+
import (
+
"context"
+
+
"tangled.sh/tangled.sh/core/appview/db"
+
issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues"
+
"tangled.sh/tangled.sh/core/appview/notify"
+
)
+
+
var _ notify.Notifier = &Indexer{}
+
+
func (ix *Indexer) NewIssue(ctx context.Context, issue *db.Issue) {
+
ix.Issues.Index(ctx, &issues_indexer.IssueData{
+
ID: issue.ID,
+
IssueID: issue.IssueId,
+
Title: issue.Title,
+
Body: issue.Body,
+
IsOpen: issue.Open,
+
Comments: []issues_indexer.IssueCommentData{},
+
})
+
}
+
+
func (ix *Indexer) NewPullComment(ctx context.Context, comment *db.PullComment) {
+
panic("unimplemented")
+
}
+4
appview/issues/issues.go
···
"tangled.sh/tangled.sh/core/api/tangled"
"tangled.sh/tangled.sh/core/appview/config"
"tangled.sh/tangled.sh/core/appview/db"
+
issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues"
"tangled.sh/tangled.sh/core/appview/notify"
"tangled.sh/tangled.sh/core/appview/oauth"
"tangled.sh/tangled.sh/core/appview/pages"
···
db *db.DB
config *config.Config
notifier notify.Notifier
+
indexer *issues_indexer.Indexer
}
func New(
···
db *db.DB,
config *config.Config,
notifier notify.Notifier,
+
indexer *issues_indexer.Indexer,
) *Issues {
return &Issues{
oauth: oauth,
···
db: db,
config: config,
notifier: notifier,
+
indexer: indexer,
}
}
+23
appview/models/search.go
···
+
package models
+
+
import "tangled.sh/tangled.sh/core/appview/pagination"
+
+
type IssueSearchOptions struct {
+
Keyword string
+
RepoAt string
+
IsOpen bool
+
+
Page pagination.Page
+
}
+
+
// func (so *SearchOptions) ToFilters() []filter {
+
// var filters []filter
+
// if so.IsOpen != nil {
+
// openValue := 0
+
// if *so.IsOpen {
+
// openValue = 1
+
// }
+
// filters = append(filters, FilterEq("open", openValue))
+
// }
+
// return filters
+
// }
+1
appview/pages/pages.go
···
Issues []db.Issue
Page pagination.Page
FilteringByOpen bool
+
FilterQuery string
}
func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error {
+1 -1
appview/state/router.go
···
}
func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler {
-
issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier)
+
issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier, s.indexer.Issues)
return issues.Router(mw)
}
+10
appview/state/state.go
···
"tangled.sh/tangled.sh/core/appview/cache/session"
"tangled.sh/tangled.sh/core/appview/config"
"tangled.sh/tangled.sh/core/appview/db"
+
"tangled.sh/tangled.sh/core/appview/indexer"
"tangled.sh/tangled.sh/core/appview/notify"
"tangled.sh/tangled.sh/core/appview/oauth"
"tangled.sh/tangled.sh/core/appview/pages"
···
type State struct {
db *db.DB
notifier notify.Notifier
+
indexer *indexer.Indexer
oauth *oauth.OAuth
enforcer *rbac.Enforcer
pages *pages.Pages
···
return nil, fmt.Errorf("failed to create db: %w", err)
}
+
indexer := indexer.New()
+
err = indexer.Init(ctx, d)
+
if err != nil {
+
return nil, fmt.Errorf("failed to create indexer: %w", err)
+
}
+
enforcer, err := rbac.NewEnforcer(config.Core.DbPath)
if err != nil {
return nil, fmt.Errorf("failed to create enforcer: %w", err)
···
if !config.Core.Dev {
notifiers = append(notifiers, posthogService.NewPosthogNotifier(posthog))
}
+
notifiers = append(notifiers, indexer)
notifier := notify.NewMergedNotifier(notifiers...)
state := &State{
d,
notifier,
+
indexer,
oauth,
enforcer,
pgs,
+27
go.mod
···
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
+
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/alecthomas/repr v0.4.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
+
github.com/bits-and-blooms/bitset v1.22.0 // indirect
+
github.com/blevesearch/bleve/v2 v2.5.3 // indirect
+
github.com/blevesearch/bleve_index_api v1.2.8 // indirect
+
github.com/blevesearch/geo v0.2.4 // indirect
+
github.com/blevesearch/go-faiss v1.0.25 // indirect
+
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
+
github.com/blevesearch/gtreap v0.1.1 // indirect
+
github.com/blevesearch/mmap-go v1.0.4 // indirect
+
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
+
github.com/blevesearch/segment v0.9.1 // indirect
+
github.com/blevesearch/snowballstem v0.9.0 // indirect
+
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
+
github.com/blevesearch/vellum v1.1.0 // indirect
+
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
+
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
+
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
+
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
+
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
+
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
···
github.com/golang-jwt/jwt/v5 v5.2.3 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/mock v1.6.0 // indirect
+
github.com/golang/protobuf v1.5.4 // indirect
+
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/feeds v1.2.0 // indirect
···
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.6.0 // indirect
github.com/ipfs/go-metrics-interface v0.3.0 // indirect
+
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
···
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
+
github.com/mschoch/smat v0.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
···
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
+
go.etcd.io/bbolt v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
+58
go.sum
···
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
+
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
+
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
···
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
+
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+
github.com/blevesearch/bleve/v2 v2.5.3 h1:9l1xtKaETv64SZc1jc4Sy0N804laSa/LeMbYddq1YEM=
+
github.com/blevesearch/bleve/v2 v2.5.3/go.mod h1:Z/e8aWjiq8HeX+nW8qROSxiE0830yQA071dwR3yoMzw=
+
github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y=
+
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
+
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
+
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
+
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
+
github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
+
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
+
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
+
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
+
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
+
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
+
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
+
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 h1:Yqk0XD1mE0fDZAJXTjawJ8If/85JxnLd8v5vG/jWE/s=
+
github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8=
+
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
+
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
+
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
+
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
+
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
+
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
+
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
+
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
+
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
+
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
+
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
+
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
+
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
+
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
+
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
+
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
+
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
+
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
+
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
+
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb h1:BqMNDZMfXwiRTJ6NvQotJ0qInn37JH5U8E+TF01CFHQ=
github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb/go.mod h1:0XUyOCRtL4/OiyeqMTmr6RlVHQMDgw3LS7CfibuZR5Q=
github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA=
···
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
···
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
···
github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
···
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
+
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
+
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
···
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
+
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
+
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
···
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=