khatru: update docs again, now it seems to be mostly up to date.

This commit is contained in:
fiatjaf
2025-10-20 18:22:08 -03:00
parent e1fbd09407
commit 8cf120c08f
10 changed files with 40 additions and 27 deletions

View File

@@ -46,9 +46,9 @@ func startPollingGame(relay *khatru.Relay) {
relay.BroadcastEvent(evt)
// just calling BroadcastEvent won't cause this event to be be stored,
// if for any reason you want to store these events you must call the store functions manually
for _, store := range relay.StoreEvent {
store(context.TODO(), evt)
// if for any reason you want to store these events you must call the store function manually
if relay.StoreEvent != nil {
relay.StoreEvent(context.TODO(), evt)
}
}
if newStatus.TeamB > current.TeamB {

View File

@@ -69,12 +69,13 @@ func handleWeatherQuery(ctx context.Context, filter nostr.Filter) iter.Seq[nostr
}
}
evt.Sign(global.RelaySecretKey)
ch <- evt
if !yield(evt) {
return
}
}
}
}
}()
return ch, nil
}
}
```

View File

@@ -16,7 +16,7 @@ func main () {
// other stuff here
}
func handleEvent(ctx context.Context, event *nostr.Event) error {
func handleEvent(ctx context.Context, event nostr.Event) error {
// store each event as a file on google drive
_, err := gdriveService.Files.Create(googledrive.CreateOptions{
Name: event.ID, // with the name set to their id
@@ -25,12 +25,9 @@ func handleEvent(ctx context.Context, event *nostr.Event) error {
return err
}
func handleQuery(ctx context.Context, filter nostr.Filter) (ch chan *nostr.Event, err error) {
// QueryEvents functions are expected to return a channel
ch := make(chan *nostr.Event)
// and they can do their query asynchronously, emitting events to the channel as they come
go func () {
func handleQuery(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
// QueryEvents functions return an iterator
return func(yield func(nostr.Event) bool) {
if len(filter.IDs) > 0 {
// if the query is for ids we can do a simpler name match
for _, id := range filter.IDS {
@@ -40,7 +37,9 @@ func handleQuery(ctx context.Context, filter nostr.Filter) (ch chan *nostr.Event
if len(results) > 0 {
var evt nostr.Event
json.Unmarshal(results[0].Body, &evt)
ch <- evt
if !yield(evt) {
return
}
}
}
} else {
@@ -53,14 +52,14 @@ func handleQuery(ctx context.Context, filter nostr.Filter) (ch chan *nostr.Event
var evt nostr.Event
json.Unmarshal(results[0].Body, &evt)
if filter.Match(evt) {
ch <- evt
if !yield(evt) {
return
}
}
}
}
}
}()
return ch, nil
}
}
```

View File

@@ -25,9 +25,24 @@ func main () {
panic(err)
}
relay.StoreEvent = policies.SeqStore(normal.SaveEvent, search.SaveEvent)
relay.QueryStored = policies.SeqQuery(normal.QueryEvents, search.QueryEvents)
relay.DeleteEvent = policies.SeqDelete(normal.DeleteEvent, search.DeleteEvent)
relay.StoreEvent = func(ctx context.Context, evt nostr.Event) error {
if err := normal.SaveEvent(evt); err != nil {
return err
}
return search.SaveEvent(evt)
}
relay.QueryStored = func(ctx context.Context, filter nostr.Filter) iter.Seq[nostr.Event] {
if filter.Search != "" {
return search.QueryEvents(filter)
}
return normal.QueryEvents(filter)
}
relay.DeleteEvent = func(ctx context.Context, id nostr.ID) error {
if err := normal.DeleteEvent(id); err != nil {
return err
}
return search.DeleteEvent(id)
}
// other stuff here
}

View File

@@ -33,7 +33,7 @@ If on `OnRequest` or `OnEvent` you prefix the message with `auth-required: `, th
relay.OnRequest = func(ctx context.Context, filter nostr.Filter) (bool, string) {
return true, "auth-required: this query requires you to be authenticated"
}
relay.OnEvent = func(ctx context.Context, event *nostr.Event) (bool, string) {
relay.OnEvent = func(ctx context.Context, event nostr.Event) (bool, string) {
return true, "auth-required: publishing this event requires authentication"
}
```

View File

@@ -63,7 +63,7 @@ func main() {
}
```
Every [`khatru.Relay`](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) instance comes with its own ['http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) inside. It ensures all requests are handled normally, but intercepts the requests that are pertinent to the relay operation, specifically the WebSocket requests, and the [NIP-11](https://nips.nostr.com/11) and the [NIP-86](https://nips.nostr.com/86) HTTP requests.
Every [`khatru.Relay`](https://pkg.go.dev/fiatjaf.com/nostr/khatru#Relay) instance comes with its own ['http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) inside. It ensures all requests are handled normally, but intercepts the requests that are pertinent to the relay operation, specifically the WebSocket requests, and the [NIP-11](https://nips.nostr.com/11) and the [NIP-86](https://nips.nostr.com/86) HTTP requests.
## Exposing multiple relays at the same path or at the root

View File

@@ -62,7 +62,7 @@ You can do a kind of sharding, for example, by storing some events in one store
For example, maybe you want kind 1 events in `db1` and kind 30023 events in `db30023`:
```go
relay.StoreEvent = func (ctx context.Context, evt *nostr.Event) error {
relay.StoreEvent = func (ctx context.Context, evt nostr.Event) error {
switch evt.Kind {
case nostr.Kind(1):
return db1.SaveEvent(evt)

View File

@@ -6,8 +6,6 @@ outline: deep
If you have one (or more) set of policies that have to be executed in sequence (for example, first you check for the presence of a tag, then later in the next policies you use that tag without checking) and they only apply to some class of events, but you still want your relay to deal with other classes of events that can lead to cumbersome sets of rules, always having to check if an event meets the requirements and so on. There is where routing can help you.
It also can be handy if you get a [`khatru.Relay`](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay) from somewhere else, like a library such as [`relay29`](https://github.com/fiatjaf/relay29), and you want to combine it with other policies without some interfering with the others. As in the example below:
```go
sk := os.Getenv("RELAY_SECRET_KEY")

View File

@@ -47,7 +47,7 @@ These are lists of functions that will be called in order every time an `EVENT`
The next step is adding some protection, because maybe we don't want to allow _anyone_ to write to our relay. Maybe we want to only allow people that have a pubkey starting with `"a"`, `"b"` or `"c"`:
```go
relay.OnEvent = func (ctx context.Context, event *nostr.Event) (reject bool, msg string) {
relay.OnEvent = func (ctx context.Context, event nostr.Event) (reject bool, msg string) {
firstHexChar := event.PubKey.Hex()[0:1]
if firstHexChar == "a" || firstHexChar == "b" || firstHexChar == "c" {
return false, "" // allow

View File

@@ -35,7 +35,7 @@ features:
details: You just define your custom handlers for each RPC call and they will be exposed appropriately to management clients.
- title: It's written in Go
icon: 🛵
link: https://pkg.go.dev/github.com/fiatjaf/khatru
link: https://pkg.go.dev/fiatjaf.com/nostr/khatru
details: That means it is fast and lightweight, you can learn the language in 5 minutes and it builds your relay into a single binary that's easy to ship and deploy.
---