sdk finally compiles.

This commit is contained in:
fiatjaf
2025-04-17 00:36:33 -03:00
parent a7be696243
commit 0130725321
15 changed files with 141 additions and 149 deletions

View File

@@ -21,16 +21,16 @@ const (
) )
func (sys *System) initializeAddressableDataloaders() { func (sys *System) initializeAddressableDataloaders() {
sys.addressableLoaders = make([]*dataloader.Loader[nostr.PubKey, []*nostr.Event], 4) sys.addressableLoaders = make([]*dataloader.Loader[nostr.PubKey, []nostr.Event], 4)
sys.addressableLoaders[kind_30000] = sys.createAddressableDataloader(30000) sys.addressableLoaders[kind_30000] = sys.createAddressableDataloader(30000)
sys.addressableLoaders[kind_30002] = sys.createAddressableDataloader(30002) sys.addressableLoaders[kind_30002] = sys.createAddressableDataloader(30002)
sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015) sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015)
sys.addressableLoaders[kind_30030] = sys.createAddressableDataloader(30030) sys.addressableLoaders[kind_30030] = sys.createAddressableDataloader(30030)
} }
func (sys *System) createAddressableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, []*nostr.Event] { func (sys *System) createAddressableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, []nostr.Event] {
return dataloader.NewBatchedLoader( return dataloader.NewBatchedLoader(
func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] { func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[[]nostr.Event] {
return sys.batchLoadAddressableEvents(ctxs, kind, pubkeys) return sys.batchLoadAddressableEvents(ctxs, kind, pubkeys)
}, },
dataloader.Options{ dataloader.Options{
@@ -44,9 +44,9 @@ func (sys *System) batchLoadAddressableEvents(
ctxs []context.Context, ctxs []context.Context,
kind uint16, kind uint16,
pubkeys []nostr.PubKey, pubkeys []nostr.PubKey,
) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] { ) map[nostr.PubKey]dataloader.Result[[]nostr.Event] {
batchSize := len(pubkeys) batchSize := len(pubkeys)
results := make(map[nostr.PubKey]dataloader.Result[[]*nostr.Event], batchSize) results := make(map[nostr.PubKey]dataloader.Result[[]nostr.Event], batchSize)
relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2)) relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2))
relayFilterIndex := make(map[string]int, max(3, batchSize*2)) relayFilterIndex := make(map[string]int, max(3, batchSize*2))
@@ -103,7 +103,9 @@ func (sys *System) batchLoadAddressableEvents(
wg.Wait() wg.Wait()
// query all relays with the prepared filters // query all relays with the prepared filters
multiSubs := sys.Pool.BatchedSubManyEose(aggregatedContext, relayFilter) multiSubs := sys.Pool.BatchedSubManyEose(aggregatedContext, relayFilter, nostr.SubscriptionOptions{
Label: "loadaddrs",
})
nextEvent: nextEvent:
for { for {
select { select {
@@ -115,7 +117,7 @@ nextEvent:
events := results[ie.PubKey].Data events := results[ie.PubKey].Data
if events == nil { if events == nil {
// no events found, so just add this and end // no events found, so just add this and end
results[ie.PubKey] = dataloader.Result[[]*nostr.Event]{Data: []*nostr.Event{ie.Event}} results[ie.PubKey] = dataloader.Result[[]nostr.Event]{Data: []nostr.Event{ie.Event}}
continue nextEvent continue nextEvent
} }
@@ -136,7 +138,7 @@ nextEvent:
} }
events = append(events, ie.Event) events = append(events, ie.Event)
results[ie.PubKey] = dataloader.Result[[]*nostr.Event]{Data: events} results[ie.PubKey] = dataloader.Result[[]nostr.Event]{Data: events}
case <-aggregatedContext.Done(): case <-aggregatedContext.Done():
return results return results
} }

View File

@@ -105,9 +105,9 @@ func (sys *System) FetchFeedPage(
kinds []uint16, kinds []uint16,
until nostr.Timestamp, until nostr.Timestamp,
totalLimit int, totalLimit int,
) ([]*nostr.Event, error) { ) ([]nostr.Event, error) {
limitPerKey := PerQueryLimitInBatch(totalLimit, len(pubkeys)) limitPerKey := PerQueryLimitInBatch(totalLimit, len(pubkeys))
events := make([]*nostr.Event, 0, len(pubkeys)*limitPerKey) events := make([]nostr.Event, 0, len(pubkeys)*limitPerKey)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(pubkeys)) wg.Add(len(pubkeys))
@@ -153,8 +153,10 @@ func (sys *System) FetchFeedPage(
fUntil := oldestTimestamp + 1 fUntil := oldestTimestamp + 1
filter.Until = &fUntil filter.Until = &fUntil
filter.Since = nil filter.Since = nil
for ie := range sys.Pool.FetchMany(ctx, relays, filter, nostr.WithLabel("feedpage")) { for ie := range sys.Pool.FetchMany(ctx, relays, filter, nostr.SubscriptionOptions{
sys.StoreRelay.Publish(ctx, *ie.Event) Label: "feedpage",
}) {
sys.Publisher.Publish(ctx, ie.Event)
// we shouldn't need this check here, but against rogue relays we'll do it // we shouldn't need this check here, but against rogue relays we'll do it
if ie.Event.CreatedAt < oldestTimestamp { if ie.Event.CreatedAt < oldestTimestamp {
@@ -173,7 +175,7 @@ func (sys *System) FetchFeedPage(
} }
wg.Wait() wg.Wait()
slices.SortFunc(events, nostr.CompareEventPtrReverse) slices.SortFunc(events, nostr.CompareEventReverse)
return events, nil return events, nil
} }

View File

@@ -2,7 +2,6 @@ package sdk
import ( import (
"context" "context"
"encoding/hex"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip05" "fiatjaf.com/nostr/nip05"
@@ -12,18 +11,15 @@ import (
// InputToProfile turns any npub/nprofile/hex/nip05 input into a ProfilePointer (or nil). // InputToProfile turns any npub/nprofile/hex/nip05 input into a ProfilePointer (or nil).
func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer { func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer {
// handle if it is a hex string // handle if it is a hex string
if len(input) == 64 { if pk, err := nostr.PubKeyFromHex(input); err == nil {
if _, err := hex.DecodeString(input); err == nil { return &nostr.ProfilePointer{PublicKey: pk}
return &nostr.ProfilePointer{PublicKey: input}
}
} }
// handle nip19 codes, if that's the case // handle nip19 codes, if that's the case
prefix, data, _ := nip19.Decode(input) prefix, data, _ := nip19.Decode(input)
switch prefix { switch prefix {
case "npub": case "npub":
input = data.(string) return &nostr.ProfilePointer{PublicKey: data.(nostr.PubKey)}
return &nostr.ProfilePointer{PublicKey: input}
case "nprofile": case "nprofile":
pp := data.(nostr.ProfilePointer) pp := data.(nostr.ProfilePointer)
return &pp return &pp
@@ -41,19 +37,15 @@ func InputToProfile(ctx context.Context, input string) *nostr.ProfilePointer {
// InputToEventPointer turns any note/nevent/hex input into a EventPointer (or nil). // InputToEventPointer turns any note/nevent/hex input into a EventPointer (or nil).
func InputToEventPointer(input string) *nostr.EventPointer { func InputToEventPointer(input string) *nostr.EventPointer {
// handle if it is a hex string // handle if it is a hex string
if len(input) == 64 { if id, err := nostr.IDFromHex(input); err == nil {
if _, err := hex.DecodeString(input); err == nil { return &nostr.EventPointer{ID: id}
return &nostr.EventPointer{ID: input}
}
} }
// handle nip19 codes, if that's the case // handle nip19 codes, if that's the case
prefix, data, _ := nip19.Decode(input) prefix, data, _ := nip19.Decode(input)
switch prefix { switch prefix {
case "note": case "note":
if input, ok := data.(string); ok { return &nostr.EventPointer{ID: data.([32]byte)}
return &nostr.EventPointer{ID: input}
}
case "nevent": case "nevent":
if ep, ok := data.(nostr.EventPointer); ok { if ep, ok := data.(nostr.EventPointer); ok {
return &ep return &ep

View File

@@ -10,15 +10,15 @@ import (
"fiatjaf.com/nostr/sdk/cache" "fiatjaf.com/nostr/sdk/cache"
) )
type GenericList[I TagItemWithValue] struct { type GenericList[V comparable, I TagItemWithValue[V]] struct {
PubKey nostr.PubKey `json:"-"` // must always be set otherwise things will break PubKey nostr.PubKey `json:"-"` // must always be set otherwise things will break
Event *nostr.Event `json:"-"` // may be empty if a contact list event wasn't found Event *nostr.Event `json:"-"` // may be empty if a contact list event wasn't found
Items []I Items []I
} }
type TagItemWithValue interface { type TagItemWithValue[V comparable] interface {
Value() any Value() V
} }
var ( var (
@@ -26,15 +26,15 @@ var (
valueWasJustCached = [60]bool{} valueWasJustCached = [60]bool{}
) )
func fetchGenericList[I TagItemWithValue]( func fetchGenericList[V comparable, I TagItemWithValue[V]](
sys *System, sys *System,
ctx context.Context, ctx context.Context,
pubkey nostr.PubKey, pubkey nostr.PubKey,
actualKind uint16, actualKind uint16,
replaceableIndex replaceableIndex, replaceableIndex replaceableIndex,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
cache cache.Cache32[GenericList[I]], cache cache.Cache32[GenericList[V, I]],
) (fl GenericList[I], fromInternal bool) { ) (fl GenericList[V, I], fromInternal bool) {
// we have 60 mutexes, so we can load up to 60 lists at the same time, but if we do the same exact // we have 60 mutexes, so we can load up to 60 lists at the same time, but if we do the same exact
// call that will do it only once, the subsequent ones will wait for a result to be cached // call that will do it only once, the subsequent ones will wait for a result to be cached
// and then return it from cache -- 13 is an arbitrary index for the pubkey // and then return it from cache -- 13 is an arbitrary index for the pubkey
@@ -55,13 +55,12 @@ func fetchGenericList[I TagItemWithValue](
return v, true return v, true
} }
v := GenericList[I]{PubKey: pubkey} v := GenericList[V, I]{PubKey: pubkey}
events, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []uint16{actualKind}, Authors: []nostr.PubKey{pubkey}}) for evt := range sys.Store.QueryEvents(nostr.Filter{Kinds: []uint16{actualKind}, Authors: []nostr.PubKey{pubkey}}) {
if len(events) != 0 {
// ok, we found something locally // ok, we found something locally
items := parseItemsFromEventTags(events[0], parseTag) items := parseItemsFromEventTags(evt, parseTag)
v.Event = events[0] v.Event = &evt
v.Items = items v.Items = items
// but if we haven't tried fetching from the network recently we should do it // but if we haven't tried fetching from the network recently we should do it
@@ -100,30 +99,30 @@ func fetchGenericList[I TagItemWithValue](
return v, false return v, false
} }
func tryFetchListFromNetwork[I TagItemWithValue]( func tryFetchListFromNetwork[V comparable, I TagItemWithValue[V]](
ctx context.Context, ctx context.Context,
sys *System, sys *System,
pubkey nostr.PubKey, pubkey nostr.PubKey,
replaceableIndex replaceableIndex, replaceableIndex replaceableIndex,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
) *GenericList[I] { ) *GenericList[V, I] {
evt, err := sys.replaceableLoaders[replaceableIndex].Load(ctx, pubkey) evt, err := sys.replaceableLoaders[replaceableIndex].Load(ctx, pubkey)
if err != nil { if err != nil {
return nil return nil
} }
v := &GenericList[I]{ v := &GenericList[V, I]{
PubKey: pubkey, PubKey: pubkey,
Event: evt, Event: &evt,
Items: parseItemsFromEventTags(evt, parseTag), Items: parseItemsFromEventTags(evt, parseTag),
} }
sys.StoreRelay.Publish(ctx, *evt) sys.Publisher.Publish(ctx, evt)
return v return v
} }
func parseItemsFromEventTags[I TagItemWithValue]( func parseItemsFromEventTags[V comparable, I TagItemWithValue[V]](
evt *nostr.Event, evt nostr.Event,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
) []I { ) []I {
result := make([]I, 0, len(evt.Tags)) result := make([]I, 0, len(evt.Tags))

View File

@@ -11,18 +11,18 @@ type EventRef struct{ nostr.Pointer }
func (e EventRef) Value() string { return e.Pointer.AsTagReference() } func (e EventRef) Value() string { return e.Pointer.AsTagReference() }
func (sys *System) FetchBookmarkList(ctx context.Context, pubkey string) GenericList[EventRef] { func (sys *System) FetchBookmarkList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
if sys.BookmarkListCache == nil { if sys.BookmarkListCache == nil {
sys.BookmarkListCache = cache_memory.New[GenericList[EventRef]](1000) sys.BookmarkListCache = cache_memory.New[GenericList[string, EventRef]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10003, kind_10003, parseEventRef, sys.BookmarkListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10003, kind_10003, parseEventRef, sys.BookmarkListCache)
return ml return ml
} }
func (sys *System) FetchPinList(ctx context.Context, pubkey string) GenericList[EventRef] { func (sys *System) FetchPinList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, EventRef] {
if sys.PinListCache == nil { if sys.PinListCache == nil {
sys.PinListCache = cache_memory.New[GenericList[EventRef]](1000) sys.PinListCache = cache_memory.New[GenericList[string, EventRef]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10001, kind_10001, parseEventRef, sys.PinListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10001, kind_10001, parseEventRef, sys.PinListCache)

View File

@@ -17,27 +17,27 @@ type ProfileRef struct {
func (f ProfileRef) Value() nostr.PubKey { return f.Pubkey } func (f ProfileRef) Value() nostr.PubKey { return f.Pubkey }
func (sys *System) FetchFollowList(ctx context.Context, pubkey nostr.PubKey) GenericList[ProfileRef] { func (sys *System) FetchFollowList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
if sys.FollowListCache == nil { if sys.FollowListCache == nil {
sys.FollowListCache = cache_memory.New[GenericList[ProfileRef]](1000) sys.FollowListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
} }
fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache) fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache)
return fl return fl
} }
func (sys *System) FetchMuteList(ctx context.Context, pubkey nostr.PubKey) GenericList[ProfileRef] { func (sys *System) FetchMuteList(ctx context.Context, pubkey nostr.PubKey) GenericList[nostr.PubKey, ProfileRef] {
if sys.MuteListCache == nil { if sys.MuteListCache == nil {
sys.MuteListCache = cache_memory.New[GenericList[ProfileRef]](1000) sys.MuteListCache = cache_memory.New[GenericList[nostr.PubKey, ProfileRef]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, kind_10000, parseProfileRef, sys.MuteListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, kind_10000, parseProfileRef, sys.MuteListCache)
return ml return ml
} }
func (sys *System) FetchFollowSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[ProfileRef] { func (sys *System) FetchFollowSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[nostr.PubKey, ProfileRef] {
if sys.FollowSetsCache == nil { if sys.FollowSetsCache == nil {
sys.FollowSetsCache = cache_memory.New[GenericSets[ProfileRef]](1000) sys.FollowSetsCache = cache_memory.New[GenericSets[nostr.PubKey, ProfileRef]](1000)
} }
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30000, kind_30000, parseProfileRef, sys.FollowSetsCache) ml, _ := fetchGenericSets(sys, ctx, pubkey, 30000, kind_30000, parseProfileRef, sys.FollowSetsCache)
@@ -52,11 +52,10 @@ func parseProfileRef(tag nostr.Tag) (fw ProfileRef, ok bool) {
return fw, false return fw, false
} }
pubkey, err := nostr.PubKeyFromHex(fw.Pubkey) pubkey, err := nostr.PubKeyFromHex(tag[1])
if err != nil { if err != nil {
return fw, false return fw, false
} }
fw.Pubkey = pubkey fw.Pubkey = pubkey
if len(tag) > 2 { if len(tag) > 2 {

View File

@@ -19,32 +19,32 @@ type RelayURL string
func (r RelayURL) Value() string { return string(r) } func (r RelayURL) Value() string { return string(r) }
func (sys *System) FetchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[Relay] { func (sys *System) FetchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, Relay] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10002, kind_10002, parseRelayFromKind10002, sys.RelayListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10002, kind_10002, parseRelayFromKind10002, sys.RelayListCache)
return ml return ml
} }
func (sys *System) FetchBlockedRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[RelayURL] { func (sys *System) FetchBlockedRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
if sys.BlockedRelayListCache == nil { if sys.BlockedRelayListCache == nil {
sys.BlockedRelayListCache = cache_memory.New[GenericList[RelayURL]](1000) sys.BlockedRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10006, kind_10006, parseRelayURL, sys.BlockedRelayListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10006, kind_10006, parseRelayURL, sys.BlockedRelayListCache)
return ml return ml
} }
func (sys *System) FetchSearchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[RelayURL] { func (sys *System) FetchSearchRelayList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, RelayURL] {
if sys.SearchRelayListCache == nil { if sys.SearchRelayListCache == nil {
sys.SearchRelayListCache = cache_memory.New[GenericList[RelayURL]](1000) sys.SearchRelayListCache = cache_memory.New[GenericList[string, RelayURL]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10007, kind_10007, parseRelayURL, sys.SearchRelayListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10007, kind_10007, parseRelayURL, sys.SearchRelayListCache)
return ml return ml
} }
func (sys *System) FetchRelaySets(ctx context.Context, pubkey nostr.PubKey) GenericSets[RelayURL] { func (sys *System) FetchRelaySets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, RelayURL] {
if sys.RelaySetsCache == nil { if sys.RelaySetsCache == nil {
sys.RelaySetsCache = cache_memory.New[GenericSets[RelayURL]](1000) sys.RelaySetsCache = cache_memory.New[GenericSets[string, RelayURL]](1000)
} }
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30002, kind_30002, parseRelayURL, sys.RelaySetsCache) ml, _ := fetchGenericSets(sys, ctx, pubkey, 30002, kind_30002, parseRelayURL, sys.RelaySetsCache)

View File

@@ -11,18 +11,18 @@ type Topic string
func (r Topic) Value() string { return string(r) } func (r Topic) Value() string { return string(r) }
func (sys *System) FetchTopicList(ctx context.Context, pubkey nostr.PubKey) GenericList[Topic] { func (sys *System) FetchTopicList(ctx context.Context, pubkey nostr.PubKey) GenericList[string, Topic] {
if sys.TopicListCache == nil { if sys.TopicListCache == nil {
sys.TopicListCache = cache_memory.New[GenericList[Topic]](1000) sys.TopicListCache = cache_memory.New[GenericList[string, Topic]](1000)
} }
ml, _ := fetchGenericList(sys, ctx, pubkey, 10015, kind_10015, parseTopicString, sys.TopicListCache) ml, _ := fetchGenericList(sys, ctx, pubkey, 10015, kind_10015, parseTopicString, sys.TopicListCache)
return ml return ml
} }
func (sys *System) FetchTopicSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[Topic] { func (sys *System) FetchTopicSets(ctx context.Context, pubkey nostr.PubKey) GenericSets[string, Topic] {
if sys.TopicSetsCache == nil { if sys.TopicSetsCache == nil {
sys.TopicSetsCache = cache_memory.New[GenericSets[Topic]](1000) sys.TopicSetsCache = cache_memory.New[GenericSets[string, Topic]](1000)
} }
ml, _ := fetchGenericSets(sys, ctx, pubkey, 30015, kind_30015, parseTopicString, sys.TopicSetsCache) ml, _ := fetchGenericSets(sys, ctx, pubkey, 30015, kind_30015, parseTopicString, sys.TopicSetsCache)

View File

@@ -110,12 +110,11 @@ func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey nostr.PubKey
pm.PubKey = pubkey pm.PubKey = pubkey
res, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []int{0}, Authors: []string{pubkey}}) for evt := range sys.Store.QueryEvents(nostr.Filter{Kinds: []uint16{0}, Authors: []nostr.PubKey{pubkey}}) {
if len(res) != 0 {
// ok, we found something locally // ok, we found something locally
pm, _ = ParseMetadata(res[0]) pm, _ = ParseMetadata(evt)
pm.PubKey = pubkey pm.PubKey = pubkey
pm.Event = res[0] pm.Event = &evt
// but if we haven't tried fetching from the network recently we should do it // but if we haven't tried fetching from the network recently we should do it
lastFetchKey := makeLastFetchKey(0, pubkey) lastFetchKey := makeLastFetchKey(0, pubkey)
@@ -151,7 +150,7 @@ func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey nostr.PubKey
return pm return pm
} }
func (sys *System) tryFetchMetadataFromNetwork(ctx context.Context, pubkey string) *ProfileMetadata { func (sys *System) tryFetchMetadataFromNetwork(ctx context.Context, pubkey nostr.PubKey) *ProfileMetadata {
evt, err := sys.replaceableLoaders[kind_0].Load(ctx, pubkey) evt, err := sys.replaceableLoaders[kind_0].Load(ctx, pubkey)
if err != nil { if err != nil {
return nil return nil
@@ -163,15 +162,15 @@ func (sys *System) tryFetchMetadataFromNetwork(ctx context.Context, pubkey strin
} }
pm.PubKey = pubkey pm.PubKey = pubkey
pm.Event = evt pm.Event = &evt
sys.StoreRelay.Publish(ctx, *evt) sys.Publisher.Publish(ctx, evt)
sys.MetadataCache.SetWithTTL(pubkey, pm, time.Hour*6) sys.MetadataCache.SetWithTTL(pubkey, pm, time.Hour*6)
return &pm return &pm
} }
// ParseMetadata parses a kind 0 event into a ProfileMetadata struct. // ParseMetadata parses a kind 0 event into a ProfileMetadata struct.
// Returns an error if the event is not kind 0 or if the content is not valid JSON. // Returns an error if the event is not kind 0 or if the content is not valid JSON.
func ParseMetadata(event *nostr.Event) (meta ProfileMetadata, err error) { func ParseMetadata(event nostr.Event) (meta ProfileMetadata, err error) {
if event.Kind != 0 { if event.Kind != 0 {
err = fmt.Errorf("event %s is kind %d, not 0", event.ID, event.Kind) err = fmt.Errorf("event %s is kind %d, not 0", event.ID, event.Kind)
} else if er := json.Unmarshal([]byte(event.Content), &meta); er != nil { } else if er := json.Unmarshal([]byte(event.Content), &meta); er != nil {
@@ -183,6 +182,6 @@ func ParseMetadata(event *nostr.Event) (meta ProfileMetadata, err error) {
} }
meta.PubKey = event.PubKey meta.PubKey = event.PubKey
meta.Event = event meta.Event = &event
return meta, err return meta, err
} }

View File

@@ -49,7 +49,7 @@ func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey nostr.PubKey, n
// FetchWriteRelays just reads relays from a kind:10002, that's the only canonical place where a user reveals // FetchWriteRelays just reads relays from a kind:10002, that's the only canonical place where a user reveals
// the relays they intend to receive notifications from. // the relays they intend to receive notifications from.
func (sys *System) FetchInboxRelays(ctx context.Context, pubkey string, n int) []string { func (sys *System) FetchInboxRelays(ctx context.Context, pubkey nostr.PubKey, n int) []string {
rl := sys.FetchRelayList(ctx, pubkey) rl := sys.FetchRelayList(ctx, pubkey)
if len(rl.Items) == 0 || len(rl.Items) > 10 { if len(rl.Items) == 0 || len(rl.Items) > 10 {
return []string{"wss://relay.damus.io", "wss://nos.lol"} return []string{"wss://relay.damus.io", "wss://nos.lol"}
@@ -70,7 +70,7 @@ func (sys *System) FetchInboxRelays(ctx context.Context, pubkey string, n int) [
// //
// Use FetchWriteRelays when deciding where to publish on behalf of a user, but FetchOutboxRelays when deciding // Use FetchWriteRelays when deciding where to publish on behalf of a user, but FetchOutboxRelays when deciding
// from where to read notes authored by other users. // from where to read notes authored by other users.
func (sys *System) FetchWriteRelays(ctx context.Context, pubkey string) []string { func (sys *System) FetchWriteRelays(ctx context.Context, pubkey nostr.PubKey) []string {
rl := sys.FetchRelayList(ctx, pubkey) rl := sys.FetchRelayList(ctx, pubkey)
relays := make([]string, 0, 7) relays := make([]string, 0, 7)

View File

@@ -33,7 +33,7 @@ const (
type EventResult dataloader.Result[*nostr.Event] type EventResult dataloader.Result[*nostr.Event]
func (sys *System) initializeReplaceableDataloaders() { func (sys *System) initializeReplaceableDataloaders() {
sys.replaceableLoaders = make([]*dataloader.Loader[nostr.PubKey, *nostr.Event], 12) sys.replaceableLoaders = make([]*dataloader.Loader[nostr.PubKey, nostr.Event], 12)
sys.replaceableLoaders[kind_0] = sys.createReplaceableDataloader(0) sys.replaceableLoaders[kind_0] = sys.createReplaceableDataloader(0)
sys.replaceableLoaders[kind_3] = sys.createReplaceableDataloader(3) sys.replaceableLoaders[kind_3] = sys.createReplaceableDataloader(3)
sys.replaceableLoaders[kind_10000] = sys.createReplaceableDataloader(10000) sys.replaceableLoaders[kind_10000] = sys.createReplaceableDataloader(10000)
@@ -48,9 +48,9 @@ func (sys *System) initializeReplaceableDataloaders() {
sys.replaceableLoaders[kind_10030] = sys.createReplaceableDataloader(10030) sys.replaceableLoaders[kind_10030] = sys.createReplaceableDataloader(10030)
} }
func (sys *System) createReplaceableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, *nostr.Event] { func (sys *System) createReplaceableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, nostr.Event] {
return dataloader.NewBatchedLoader( return dataloader.NewBatchedLoader(
func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[*nostr.Event] { func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[nostr.Event] {
return sys.batchLoadReplaceableEvents(ctxs, kind, pubkeys) return sys.batchLoadReplaceableEvents(ctxs, kind, pubkeys)
}, },
dataloader.Options{ dataloader.Options{
@@ -64,9 +64,9 @@ func (sys *System) batchLoadReplaceableEvents(
ctxs []context.Context, ctxs []context.Context,
kind uint16, kind uint16,
pubkeys []nostr.PubKey, pubkeys []nostr.PubKey,
) map[nostr.PubKey]dataloader.Result[*nostr.Event] { ) map[nostr.PubKey]dataloader.Result[nostr.Event] {
batchSize := len(pubkeys) batchSize := len(pubkeys)
results := make(map[nostr.PubKey]dataloader.Result[*nostr.Event], batchSize) results := make(map[nostr.PubKey]dataloader.Result[nostr.Event], batchSize)
relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2)) relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2))
relayFilterIndex := make(map[string]int, max(3, batchSize*2)) relayFilterIndex := make(map[string]int, max(3, batchSize*2))
@@ -121,9 +121,9 @@ func (sys *System) batchLoadReplaceableEvents(
// query all relays with the prepared filters // query all relays with the prepared filters
wg.Wait() wg.Wait()
multiSubs := sys.Pool.BatchedSubManyEose(aggregatedContext, relayFilter, multiSubs := sys.Pool.BatchedSubManyEose(aggregatedContext, relayFilter, nostr.SubscriptionOptions{
nostr.WithLabel("repl~"+strconv.Itoa(int(kind))), Label: "repl~" + strconv.Itoa(int(kind)),
) })
for { for {
select { select {
case ie, more := <-multiSubs: case ie, more := <-multiSubs:
@@ -132,8 +132,8 @@ func (sys *System) batchLoadReplaceableEvents(
} }
// insert this event at the desired position // insert this event at the desired position
if val, ok := results[ie.PubKey]; !ok || val.Data == nil || val.Data.CreatedAt < ie.CreatedAt { if val, ok := results[ie.PubKey]; !ok || val.Data.ID == nostr.ZeroID || val.Data.CreatedAt < ie.CreatedAt {
results[ie.PubKey] = dataloader.Result[*nostr.Event]{Data: ie.Event} results[ie.PubKey] = dataloader.Result[nostr.Event]{Data: ie.Event}
} }
case <-aggregatedContext.Done(): case <-aggregatedContext.Done():
return results return results

View File

@@ -13,7 +13,9 @@ func (sys *System) SearchUsers(ctx context.Context, query string) []ProfileMetad
for ie := range sys.Pool.FetchMany(ctx, sys.UserSearchRelays.URLs, nostr.Filter{ for ie := range sys.Pool.FetchMany(ctx, sys.UserSearchRelays.URLs, nostr.Filter{
Search: query, Search: query,
Limit: limit, Limit: limit,
}, nostr.WithLabel("search")) { }, nostr.SubscriptionOptions{
Label: "search",
}) {
m, _ := ParseMetadata(ie.Event) m, _ := ParseMetadata(ie.Event)
profiles = append(profiles, m) profiles = append(profiles, m)
} }

View File

@@ -11,22 +11,22 @@ import (
// this is similar to list.go and inherits code from that. // this is similar to list.go and inherits code from that.
type GenericSets[I TagItemWithValue] struct { type GenericSets[V comparable, I TagItemWithValue[V]] struct {
PubKey nostr.PubKey `json:"-"` PubKey nostr.PubKey `json:"-"`
Events []*nostr.Event `json:"-"` Events []nostr.Event `json:"-"`
Sets map[string][]I Sets map[string][]I
} }
func fetchGenericSets[I TagItemWithValue]( func fetchGenericSets[V comparable, I TagItemWithValue[V]](
sys *System, sys *System,
ctx context.Context, ctx context.Context,
pubkey nostr.PubKey, pubkey nostr.PubKey,
actualKind uint16, actualKind uint16,
addressableIndex addressableIndex, addressableIndex addressableIndex,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
cache cache.Cache32[GenericSets[I]], cache cache.Cache32[GenericSets[V, I]],
) (fl GenericSets[I], fromInternal bool) { ) (fl GenericSets[V, I], fromInternal bool) {
n := pubkey[7] n := pubkey[7]
lockIdx := (uint16(n) + actualKind) % 60 lockIdx := (uint16(n) + actualKind) % 60
genericListMutexes[lockIdx].Lock() genericListMutexes[lockIdx].Lock()
@@ -44,9 +44,11 @@ func fetchGenericSets[I TagItemWithValue](
return v, true return v, true
} }
v := GenericSets[I]{PubKey: pubkey} v := GenericSets[V, I]{PubKey: pubkey}
events, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []uint16{actualKind}, Authors: []nostr.PubKey{pubkey}}) events := slices.Collect(
sys.Store.QueryEvents(nostr.Filter{Kinds: []uint16{actualKind}, Authors: []nostr.PubKey{pubkey}}),
)
if len(events) != 0 { if len(events) != 0 {
// ok, we found something locally // ok, we found something locally
sets := parseSetsFromEvents(events, parseTag) sets := parseSetsFromEvents(events, parseTag)
@@ -89,31 +91,31 @@ func fetchGenericSets[I TagItemWithValue](
return v, false return v, false
} }
func tryFetchSetsFromNetwork[I TagItemWithValue]( func tryFetchSetsFromNetwork[V comparable, I TagItemWithValue[V]](
ctx context.Context, ctx context.Context,
sys *System, sys *System,
pubkey nostr.PubKey, pubkey nostr.PubKey,
addressableIndex addressableIndex, addressableIndex addressableIndex,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
) *GenericSets[I] { ) *GenericSets[V, I] {
events, err := sys.addressableLoaders[addressableIndex].Load(ctx, pubkey) events, err := sys.addressableLoaders[addressableIndex].Load(ctx, pubkey)
if err != nil { if err != nil {
return nil return nil
} }
v := &GenericSets[I]{ v := &GenericSets[V, I]{
PubKey: pubkey, PubKey: pubkey,
Events: events, Events: events,
Sets: parseSetsFromEvents(events, parseTag), Sets: parseSetsFromEvents(events, parseTag),
} }
for _, evt := range events { for _, evt := range events {
sys.StoreRelay.Publish(ctx, *evt) sys.Publisher.Publish(ctx, evt)
} }
return v return v
} }
func parseSetsFromEvents[I TagItemWithValue]( func parseSetsFromEvents[V comparable, I TagItemWithValue[V]](
events []*nostr.Event, events []nostr.Event,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
) map[string][]I { ) map[string][]I {
sets := make(map[string][]I, len(events)) sets := make(map[string][]I, len(events))

View File

@@ -39,13 +39,13 @@ func (sys *System) FetchSpecificEventFromInput(
case "naddr": case "naddr":
pointer = data.(nostr.EntityPointer) pointer = data.(nostr.EntityPointer)
case "note": case "note":
pointer = nostr.EventPointer{ID: data.(string)} pointer = nostr.EventPointer{ID: data.([32]byte)}
default: default:
return nil, nil, fmt.Errorf("invalid code '%s'", input) return nil, nil, fmt.Errorf("invalid code '%s'", input)
} }
} else { } else {
if nostr.IsValid32ByteHex(input) { if id, err := nostr.IDFromHex(input); err == nil {
pointer = nostr.EventPointer{ID: input} pointer = nostr.EventPointer{ID: id}
} else { } else {
return nil, nil, fmt.Errorf("failed to decode '%s': %w", input, err) return nil, nil, fmt.Errorf("failed to decode '%s': %w", input, err)
} }
@@ -66,7 +66,7 @@ func (sys *System) FetchSpecificEvent(
priorityRelays := make([]string, 0, 8) priorityRelays := make([]string, 0, 8)
var filter nostr.Filter var filter nostr.Filter
author := "" var author nostr.PubKey
relays := make([]string, 0, 10) relays := make([]string, 0, 10)
fallback := make([]string, 0, 10) fallback := make([]string, 0, 10)
successRelays = make([]string, 0, 10) successRelays = make([]string, 0, 10)
@@ -74,7 +74,7 @@ func (sys *System) FetchSpecificEvent(
switch v := pointer.(type) { switch v := pointer.(type) {
case nostr.EventPointer: case nostr.EventPointer:
author = v.Author author = v.Author
filter.IDs = []string{v.ID} filter.IDs = []nostr.ID{v.ID}
relays = append(relays, v.Relays...) relays = append(relays, v.Relays...)
relays = appendUnique(relays, sys.FallbackRelays.Next()) relays = appendUnique(relays, sys.FallbackRelays.Next())
fallback = append(fallback, sys.JustIDRelays.URLs...) fallback = append(fallback, sys.JustIDRelays.URLs...)
@@ -82,9 +82,9 @@ func (sys *System) FetchSpecificEvent(
priorityRelays = append(priorityRelays, v.Relays...) priorityRelays = append(priorityRelays, v.Relays...)
case nostr.EntityPointer: case nostr.EntityPointer:
author = v.PublicKey author = v.PublicKey
filter.Authors = []string{v.PublicKey} filter.Authors = []nostr.PubKey{v.PublicKey}
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}} filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
filter.Kinds = []int{v.Kind} filter.Kinds = []uint16{v.Kind}
relays = append(relays, v.Relays...) relays = append(relays, v.Relays...)
relays = appendUnique(relays, sys.FallbackRelays.Next()) relays = appendUnique(relays, sys.FallbackRelays.Next())
fallback = append(fallback, sys.FallbackRelays.Next(), sys.FallbackRelays.Next()) fallback = append(fallback, sys.FallbackRelays.Next(), sys.FallbackRelays.Next())
@@ -93,13 +93,12 @@ func (sys *System) FetchSpecificEvent(
// try to fetch in our internal eventstore first // try to fetch in our internal eventstore first
if !params.SkipLocalStore { if !params.SkipLocalStore {
if res, _ := sys.StoreRelay.QuerySync(ctx, filter); len(res) != 0 { for evt := range sys.Store.QueryEvents(filter) {
evt := res[0] return &evt, nil, nil
return evt, nil, nil
} }
} }
if author != "" { if author != nostr.ZeroPK {
// fetch relays for author // fetch relays for author
authorRelays := sys.FetchOutboxRelays(ctx, author, 3) authorRelays := sys.FetchOutboxRelays(ctx, author, 3)
@@ -141,19 +140,16 @@ attempts:
countdown := 6.0 countdown := 6.0
subManyCtx := ctx subManyCtx := ctx
for ie := range sys.Pool.FetchMany( for ie := range sys.Pool.FetchMany(subManyCtx, attempt.relays, filter, nostr.SubscriptionOptions{
subManyCtx, Label: attempt.label,
attempt.relays, }) {
filter,
nostr.WithLabel(attempt.label),
) {
fetchProfileOnce.Do(func() { fetchProfileOnce.Do(func() {
go sys.FetchProfileMetadata(ctx, ie.PubKey) go sys.FetchProfileMetadata(ctx, ie.PubKey)
}) })
successRelays = append(successRelays, ie.Relay.URL) successRelays = append(successRelays, ie.Relay.URL)
if result == nil || ie.CreatedAt > result.CreatedAt { if result == nil || ie.CreatedAt > result.CreatedAt {
result = ie.Event result = &ie.Event
} }
if !attempt.slowWithRelays { if !attempt.slowWithRelays {
@@ -170,7 +166,7 @@ attempts:
// save stuff in cache and in internal store // save stuff in cache and in internal store
if !params.SkipLocalStore { if !params.SkipLocalStore {
sys.StoreRelay.Publish(ctx, *result) sys.Publisher.Publish(ctx, *result)
} }
// put priority relays first so they get used in nevent and nprofile // put priority relays first so they get used in nevent and nprofile

View File

@@ -1,10 +1,11 @@
package sdk package sdk
import ( import (
"context"
"math/rand/v2" "math/rand/v2"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore"
"fiatjaf.com/nostr/eventstore/nullstore"
"fiatjaf.com/nostr/eventstore/wrappers" "fiatjaf.com/nostr/eventstore/wrappers"
"fiatjaf.com/nostr/sdk/cache" "fiatjaf.com/nostr/sdk/cache"
cache_memory "fiatjaf.com/nostr/sdk/cache/memory" cache_memory "fiatjaf.com/nostr/sdk/cache/memory"
@@ -13,8 +14,6 @@ import (
"fiatjaf.com/nostr/sdk/hints/memoryh" "fiatjaf.com/nostr/sdk/hints/memoryh"
"fiatjaf.com/nostr/sdk/kvstore" "fiatjaf.com/nostr/sdk/kvstore"
kvstore_memory "fiatjaf.com/nostr/sdk/kvstore/memory" kvstore_memory "fiatjaf.com/nostr/sdk/kvstore/memory"
"fiatjaf.com/nostr/eventstore"
"fiatjaf.com/nostr/eventstore/nullstore"
) )
// System represents the core functionality of the SDK, providing access to // System represents the core functionality of the SDK, providing access to
@@ -30,17 +29,17 @@ import (
type System struct { type System struct {
KVStore kvstore.KVStore KVStore kvstore.KVStore
MetadataCache cache.Cache32[ProfileMetadata] MetadataCache cache.Cache32[ProfileMetadata]
RelayListCache cache.Cache32[GenericList[Relay]] RelayListCache cache.Cache32[GenericList[string, Relay]]
FollowListCache cache.Cache32[GenericList[ProfileRef]] FollowListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
MuteListCache cache.Cache32[GenericList[ProfileRef]] MuteListCache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]
BookmarkListCache cache.Cache32[GenericList[EventRef]] BookmarkListCache cache.Cache32[GenericList[string, EventRef]]
PinListCache cache.Cache32[GenericList[EventRef]] PinListCache cache.Cache32[GenericList[string, EventRef]]
BlockedRelayListCache cache.Cache32[GenericList[RelayURL]] BlockedRelayListCache cache.Cache32[GenericList[string, RelayURL]]
SearchRelayListCache cache.Cache32[GenericList[RelayURL]] SearchRelayListCache cache.Cache32[GenericList[string, RelayURL]]
TopicListCache cache.Cache32[GenericList[Topic]] TopicListCache cache.Cache32[GenericList[string, Topic]]
RelaySetsCache cache.Cache32[GenericSets[RelayURL]] RelaySetsCache cache.Cache32[GenericSets[string, RelayURL]]
FollowSetsCache cache.Cache32[GenericSets[ProfileRef]] FollowSetsCache cache.Cache32[GenericSets[nostr.PubKey, ProfileRef]]
TopicSetsCache cache.Cache32[GenericSets[Topic]] TopicSetsCache cache.Cache32[GenericSets[string, Topic]]
Hints hints.HintsDB Hints hints.HintsDB
Pool *nostr.Pool Pool *nostr.Pool
RelayListRelays *RelayStream RelayListRelays *RelayStream
@@ -54,8 +53,8 @@ type System struct {
Publisher wrappers.StorePublisher Publisher wrappers.StorePublisher
replaceableLoaders []*dataloader.Loader[nostr.PubKey, *nostr.Event] replaceableLoaders []*dataloader.Loader[nostr.PubKey, nostr.Event]
addressableLoaders []*dataloader.Loader[nostr.PubKey, []*nostr.Event] addressableLoaders []*dataloader.Loader[nostr.PubKey, []nostr.Event]
} }
// SystemModifier is a function that modifies a System instance. // SystemModifier is a function that modifies a System instance.
@@ -119,12 +118,12 @@ func NewSystem(mods ...SystemModifier) *System {
Hints: memoryh.NewHintDB(), Hints: memoryh.NewHintDB(),
} }
sys.Pool = nostr.NewPool(context.Background(), sys.Pool = nostr.NewPool(nostr.PoolOptions{
nostr.WithAuthorKindQueryMiddleware(sys.TrackQueryAttempts), AuthorKindQueryMiddleware: sys.TrackQueryAttempts,
nostr.WithEventMiddleware(sys.TrackEventHintsAndRelays), EventMiddleware: sys.TrackEventHintsAndRelays,
nostr.WithDuplicateMiddleware(sys.TrackEventRelaysD), DuplicateMiddleware: sys.TrackEventRelaysD,
nostr.WithPenaltyBox(), PenaltyBox: true,
) })
for _, mod := range mods { for _, mod := range mods {
mod(sys) mod(sys)
@@ -134,14 +133,14 @@ func NewSystem(mods ...SystemModifier) *System {
sys.MetadataCache = cache_memory.New[ProfileMetadata](8000) sys.MetadataCache = cache_memory.New[ProfileMetadata](8000)
} }
if sys.RelayListCache == nil { if sys.RelayListCache == nil {
sys.RelayListCache = cache_memory.New[GenericList[Relay]](8000) sys.RelayListCache = cache_memory.New[GenericList[string, Relay]](8000)
} }
if sys.Store == nil { if sys.Store == nil {
sys.Store = &nullstore.NullStore{} sys.Store = &nullstore.NullStore{}
sys.Store.Init() sys.Store.Init()
} }
sys.StoreRelay = eventstore.RelayWrapper{Store: sys.Store} sys.Publisher = wrappers.StorePublisher{Store: sys.Store}
sys.initializeReplaceableDataloaders() sys.initializeReplaceableDataloaders()
sys.initializeAddressableDataloaders() sys.initializeAddressableDataloaders()
@@ -223,14 +222,14 @@ func WithStore(store eventstore.Store) SystemModifier {
} }
// WithRelayListCache returns a SystemModifier that sets the RelayListCache. // WithRelayListCache returns a SystemModifier that sets the RelayListCache.
func WithRelayListCache(cache cache.Cache32[GenericList[Relay]]) SystemModifier { func WithRelayListCache(cache cache.Cache32[GenericList[string, Relay]]) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.RelayListCache = cache sys.RelayListCache = cache
} }
} }
// WithFollowListCache returns a SystemModifier that sets the FollowListCache. // WithFollowListCache returns a SystemModifier that sets the FollowListCache.
func WithFollowListCache(cache cache.Cache32[GenericList[ProfileRef]]) SystemModifier { func WithFollowListCache(cache cache.Cache32[GenericList[nostr.PubKey, ProfileRef]]) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.FollowListCache = cache sys.FollowListCache = cache
} }