Skip to content

Commit 1b5b2c4

Browse files
committed
feat: add type-safe SharedStore helpers
- Add type-specific getters (GetString, GetInt, GetFloat64, GetBool, GetSlice, GetMap) - Add getters with custom defaults (GetStringOr, GetIntOr, etc.) - Add Bind/MustBind methods for complex type binding (similar to Echo framework) - Add utility methods (Has, Delete, Clear, Keys, Len) - Automatic numeric type conversions for GetInt and GetFloat64 - Comprehensive test coverage for all new methods - Update documentation and cookbook examples to use new helpers - 100% backward compatible - existing Get/Set methods unchanged This enhancement makes SharedStore access safer and more ergonomic by eliminating the need for manual type assertions and reducing boilerplate code.
1 parent 55c733a commit 1b5b2c4

File tree

13 files changed

+942
-80
lines changed

13 files changed

+942
-80
lines changed

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,49 @@ err := flow.Run(ctx, shared)
165165

166166
### Shared Store
167167

168-
Thread-safe data sharing between nodes:
168+
Thread-safe data sharing between nodes with type-safe helpers:
169169

170170
```go
171171
shared := flyt.NewSharedStore()
172172

173-
// Set and get individual values
173+
// Basic get/set operations
174174
shared.Set("key", "value")
175175
value, ok := shared.Get("key")
176176

177+
// Type-safe getters (return zero values if not found or wrong type)
178+
str := shared.GetString("name") // Returns "" if not found
179+
num := shared.GetInt("count") // Returns 0 if not found
180+
price := shared.GetFloat64("price") // Returns 0.0 if not found
181+
enabled := shared.GetBool("enabled") // Returns false if not found
182+
items := shared.GetSlice("items") // Returns nil if not found
183+
config := shared.GetMap("config") // Returns nil if not found
184+
185+
// Type-safe getters with custom defaults
186+
str = shared.GetStringOr("name", "anonymous")
187+
num = shared.GetIntOr("count", -1)
188+
price = shared.GetFloat64Or("price", 99.99)
189+
enabled = shared.GetBoolOr("enabled", true)
190+
191+
// Bind complex types (similar to Echo framework)
192+
type User struct {
193+
ID int `json:"id"`
194+
Name string `json:"name"`
195+
}
196+
197+
shared.Set("user", map[string]any{"id": 123, "name": "Alice"})
198+
199+
var user User
200+
err := shared.Bind("user", &user) // Binds map to struct
201+
// Or panic on failure (for required data)
202+
shared.MustBind("user", &user)
203+
204+
// Utility methods
205+
exists := shared.Has("key") // Check if key exists
206+
shared.Delete("key") // Remove a key
207+
keys := shared.Keys() // Get all keys
208+
length := shared.Len() // Get number of items
209+
shared.Clear() // Remove all items
210+
177211
// Get all data as a map (returns a copy)
178212
allData := shared.GetAll()
179213

@@ -184,6 +218,11 @@ shared.Merge(map[string]any{
184218
})
185219
```
186220

221+
The type-safe getters handle numeric conversions automatically:
222+
- `GetInt()` converts from int8, int16, int32, int64, uint variants, and float types
223+
- `GetFloat64()` converts from all numeric types including int and float32
224+
- `GetSlice()` uses the same conversion logic as `ToSlice()` utility
225+
187226
## Intermediate Patterns
188227

189228
### Configuration via Closures

cookbook/agent/main.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ func main() {
4646
log.Fatalf("Error running agent: %v", err)
4747
}
4848

49-
answerVal, _ := shared.Get("answer")
50-
answer, ok := answerVal.(string)
51-
if !ok || answer == "" {
49+
answer := shared.GetString("answer")
50+
if answer == "" {
5251
log.Fatal("No answer found")
5352
}
5453
fmt.Println("\n🎯 Final Answer:")

cookbook/agent/nodes.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,9 @@ import (
1414
func NewDecideActionNode(llm *LLM) flyt.Node {
1515
return flyt.NewNode(
1616
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
17-
question, _ := shared.Get("question")
18-
context, _ := shared.Get("context")
19-
if context == nil {
20-
context = "No previous search"
21-
}
22-
23-
searchCount, _ := shared.Get("search_count")
24-
if searchCount == nil {
25-
searchCount = 0
26-
}
17+
question := shared.GetString("question")
18+
context := shared.GetStringOr("context", "No previous search")
19+
searchCount := shared.GetInt("search_count")
2720

2821
return map[string]any{
2922
"question": question,
@@ -112,7 +105,7 @@ Example responses:
112105
func NewSearchWebNode(searcher Searcher) flyt.Node {
113106
return flyt.NewNode(
114107
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
115-
query, _ := shared.Get("search_query")
108+
query := shared.GetString("search_query")
116109

117110
return map[string]any{
118111
"query": query,
@@ -140,13 +133,9 @@ func NewSearchWebNode(searcher Searcher) flyt.Node {
140133
results := data["results"].(string)
141134
query := data["query"].(string)
142135

143-
previousContext, _ := shared.Get("context")
144-
previousStr := ""
145-
if previousContext != nil {
146-
previousStr = previousContext.(string)
147-
}
136+
previousContext := shared.GetString("context")
148137

149-
newContext := fmt.Sprintf("%s\n\nSEARCH: %s\nRESULTS: %s", previousStr, query, results)
138+
newContext := fmt.Sprintf("%s\n\nSEARCH: %s\nRESULTS: %s", previousContext, query, results)
150139
shared.Set("context", newContext)
151140

152141
fmt.Println("📚 Found information, analyzing results...")
@@ -160,8 +149,8 @@ func NewSearchWebNode(searcher Searcher) flyt.Node {
160149
func NewAnswerQuestionNode(llm *LLM) flyt.Node {
161150
return flyt.NewNode(
162151
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
163-
question, _ := shared.Get("question")
164-
context, _ := shared.Get("context")
152+
question := shared.GetString("question")
153+
context := shared.GetString("context")
165154

166155
return map[string]any{
167156
"question": question,

cookbook/slack-bot/main.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,8 @@ func (b *SlackBot) processWithFlyt(ctx context.Context, message, channel, thread
231231
}
232232

233233
// Get response from shared store
234-
if response, ok := shared.Get("response"); ok {
235-
if responseStr, ok := response.(string); ok {
236-
b.sendMessage(channel, responseStr, threadTS)
237-
}
234+
if response := shared.GetString("response"); response != "" {
235+
b.sendMessage(channel, response, threadTS)
238236
}
239237
}
240238

cookbook/slack-bot/nodes.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@ type ParseMessageNode struct {
1616
}
1717

1818
func (n *ParseMessageNode) Prep(ctx context.Context, shared *flyt.SharedStore) (any, error) {
19-
message, ok := shared.Get("message")
20-
if !ok {
19+
messageStr := shared.GetString("message")
20+
if messageStr == "" {
2121
return nil, fmt.Errorf("no message found in shared store")
2222
}
2323

24-
messageStr, ok := message.(string)
25-
if !ok {
26-
return nil, fmt.Errorf("message is not a string")
27-
}
28-
2924
// Clean up the message (remove bot mentions, extra spaces, etc.)
3025
cleanedMessage := strings.TrimSpace(messageStr)
3126
cleanedMessage = strings.ReplaceAll(cleanedMessage, " ", " ")
@@ -67,16 +62,17 @@ type LLMNode struct {
6762

6863
func (n *LLMNode) Prep(ctx context.Context, shared *flyt.SharedStore) (any, error) {
6964
// Check if we're processing tool responses
70-
if toolResponses, ok := shared.Get("tool_responses"); ok {
65+
if shared.Has("tool_responses") {
66+
toolResponses, _ := shared.Get("tool_responses")
7167
return map[string]interface{}{
7268
"type": "tool_response",
7369
"tool_responses": toolResponses,
7470
}, nil
7571
}
7672

7773
// Otherwise, process user message
78-
message, ok := shared.Get("cleaned_message")
79-
if !ok {
74+
message := shared.GetString("cleaned_message")
75+
if message == "" {
8076
return nil, fmt.Errorf("no cleaned message found")
8177
}
8278

@@ -203,8 +199,8 @@ type FormatResponseNode struct {
203199
}
204200

205201
func (n *FormatResponseNode) Prep(ctx context.Context, shared *flyt.SharedStore) (any, error) {
206-
response, ok := shared.Get("response")
207-
if !ok {
202+
response := shared.GetString("response")
203+
if response == "" {
208204
return nil, fmt.Errorf("no response found")
209205
}
210206
return response, nil

0 commit comments

Comments
 (0)