From add987e860bdc573c6db834a6811c984d28cd2db Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 16 Apr 2026 11:01:36 -0700 Subject: [PATCH 1/2] feat(kv): Asyncify key-value Signed-off-by: Adam Reese --- examples/key-value/README.md | 2 +- examples/key-value/spin.toml | 2 +- kv/kv.go | 21 ++++++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/examples/key-value/README.md b/examples/key-value/README.md index 40751739..d3c63e97 100644 --- a/examples/key-value/README.md +++ b/examples/key-value/README.md @@ -11,7 +11,7 @@ spin up --build In another terminal window, you can interact with the Spin app: ```sh -curl localhost:3000/hello +curl localhost:3000 ``` You should receive the following output: diff --git a/examples/key-value/spin.toml b/examples/key-value/spin.toml index f87042e4d..ffd2fa3e 100644 --- a/examples/key-value/spin.toml +++ b/examples/key-value/spin.toml @@ -7,7 +7,7 @@ name = "hello-kv" version = "1.0.0" [[trigger.http]] -route = "/hello" +route = "/..." component = "hello" [component.hello] diff --git a/kv/kv.go b/kv/kv.go index 6bebc3f8..1178316f 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -4,7 +4,7 @@ package kv import ( "fmt" - keyvalue "github.com/spinframework/spin-go-sdk/v3/imports/fermyon_spin_2_0_0_key_value" + keyvalue "github.com/spinframework/spin-go-sdk/v3/imports/spin_key_value_3_0_0_key_value" ) // Store represents a connection to a key-value store. @@ -78,12 +78,27 @@ func (s *Store) Exists(key string) (bool, error) { // GetKeys returns all the keys from the store. func (s *Store) GetKeys() ([]string, error) { - result := s.store.GetKeys() + stream, future := s.store.GetKeys() + defer stream.Drop() + + var keys []string + buf := make([]string, 64) + for { + n := stream.Read(buf) + if n > 0 { + keys = append(keys, buf[:n]...) + } + if stream.WriterDropped() { + break + } + } + + result := future.Read() if result.IsErr() { return nil, errorVariantToError(result.Err()) } - return result.Ok(), nil + return keys, nil } func errorVariantToError(code keyvalue.Error) error { From 5424e4aa68f50440765de362c2bfe7307bf9272a Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 30 Apr 2026 10:13:23 -0700 Subject: [PATCH 2/2] Implement iter for kv.GetKeys() Signed-off-by: Adam Reese --- examples/key-value/main.go | 11 +++++---- kv/kv.go | 47 ++++++++++++++++++++++---------------- testdata/key-value/main.go | 11 +++++---- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/examples/key-value/main.go b/examples/key-value/main.go index 9ac000e8..60e15155 100644 --- a/examples/key-value/main.go +++ b/examples/key-value/main.go @@ -34,10 +34,13 @@ func init() { return } - keys, err := store.GetKeys() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + var keys []string + for key, err := range store.GetKeys() { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + keys = append(keys, key) } w.Header().Set("Content-Type", "application/json") diff --git a/kv/kv.go b/kv/kv.go index 1178316f..55f44e27 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -3,6 +3,7 @@ package kv import ( "fmt" + "iter" keyvalue "github.com/spinframework/spin-go-sdk/v3/imports/spin_key_value_3_0_0_key_value" ) @@ -76,29 +77,35 @@ func (s *Store) Exists(key string) (bool, error) { return result.Ok(), nil } -// GetKeys returns all the keys from the store. -func (s *Store) GetKeys() ([]string, error) { - stream, future := s.store.GetKeys() - defer stream.Drop() - - var keys []string - buf := make([]string, 64) - for { - n := stream.Read(buf) - if n > 0 { - keys = append(keys, buf[:n]...) - } - if stream.WriterDropped() { - break +// GetKeys returns an iterator over the keys in the store. Keys are yielded as +// they arrive from the host, allowing the consumer to process them +// concurrently with the underlying stream read. +// +// The iterator yields each key with a nil error. If the host reports an error +// after the stream completes, a final pair of ("", err) is yielded. Stopping +// the iteration early releases the underlying stream. +func (s *Store) GetKeys() iter.Seq2[string, error] { + return func(yield func(string, error) bool) { + stream, future := s.store.GetKeys() + defer stream.Drop() + + buf := make([]string, 64) + for { + n := stream.Read(buf) + for _, k := range buf[:n] { + if !yield(k, nil) { + return + } + } + if stream.WriterDropped() { + break + } } - } - result := future.Read() - if result.IsErr() { - return nil, errorVariantToError(result.Err()) + if result := future.Read(); result.IsErr() { + yield("", errorVariantToError(result.Err())) + } } - - return keys, nil } func errorVariantToError(code keyvalue.Error) error { diff --git a/testdata/key-value/main.go b/testdata/key-value/main.go index 9ac000e8..60e15155 100644 --- a/testdata/key-value/main.go +++ b/testdata/key-value/main.go @@ -34,10 +34,13 @@ func init() { return } - keys, err := store.GetKeys() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + var keys []string + for key, err := range store.GetKeys() { + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + keys = append(keys, key) } w.Header().Set("Content-Type", "application/json")