1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-06-30 22:33:49 +02:00

GenAI interface (#2790)

* genai interface

* x

* x

* text to speech

* Re-add events package (#2761)

* Re-add events package

* run redis as a dep

* remove redis events

* fix: data race on event subscriber

* fix: data race in tests

* fix: store errors

* fix: lint issues

* feat: default stream

* Update file.go

---------

Co-authored-by: Brian Ketelsen <bketelsen@gmail.com>

* .

* copilot couldn't make it compile so I did

* copilot couldn't make it compile so I did

* x

---------

Co-authored-by: Brian Ketelsen <bketelsen@gmail.com>
This commit is contained in:
Asim Aslam
2025-06-20 10:24:31 +01:00
committed by GitHub
parent 7e1bba2baf
commit ee9f3afe37
17 changed files with 543 additions and 21 deletions

View File

@ -362,7 +362,6 @@ func BenchmarkSub32(b *testing.B) {
sub(b, 32) sub(b, 32)
} }
func BenchmarkPub1(b *testing.B) { func BenchmarkPub1(b *testing.B) {
pub(b, 1) pub(b, 1)
} }

View File

@ -45,8 +45,6 @@ type publication struct {
err error err error
} }
func (p *publication) Ack() error { func (p *publication) Ack() error {
return p.d.Ack(false) return p.d.Ack(false)
} }

View File

@ -4,17 +4,12 @@ package cmd
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"os"
"sort" "sort"
"os"
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go-micro.dev/v5/auth"
nbroker "go-micro.dev/v5/broker/nats"
rabbit "go-micro.dev/v5/broker/rabbitmq"
"go-micro.dev/v5/broker"
"go-micro.dev/v5/cache" "go-micro.dev/v5/cache"
"go-micro.dev/v5/cache/redis" "go-micro.dev/v5/cache/redis"
"go-micro.dev/v5/client" "go-micro.dev/v5/client"
@ -26,6 +21,13 @@ import (
"go-micro.dev/v5/events" "go-micro.dev/v5/events"
"go-micro.dev/v5/logger" "go-micro.dev/v5/logger"
mprofile "go-micro.dev/v5/profile" mprofile "go-micro.dev/v5/profile"
"go-micro.dev/v5/auth"
"go-micro.dev/v5/broker"
nbroker "go-micro.dev/v5/broker/nats"
rabbit "go-micro.dev/v5/broker/rabbitmq"
"go-micro.dev/v5/genai"
"go-micro.dev/v5/genai/gemini"
"go-micro.dev/v5/genai/openai"
"go-micro.dev/v5/registry" "go-micro.dev/v5/registry"
"go-micro.dev/v5/registry/consul" "go-micro.dev/v5/registry/consul"
"go-micro.dev/v5/registry/etcd" "go-micro.dev/v5/registry/etcd"
@ -246,6 +248,21 @@ var (
EnvVars: []string{"MICRO_CONFIG"}, EnvVars: []string{"MICRO_CONFIG"},
Usage: "The source of the config to be used to get configuration", Usage: "The source of the config to be used to get configuration",
}, },
&cli.StringFlag{
Name: "genai",
EnvVars: []string{"MICRO_GENAI"},
Usage: "GenAI provider to use (e.g. openai, gemini, noop)",
},
&cli.StringFlag{
Name: "genai_key",
EnvVars: []string{"MICRO_GENAI_KEY"},
Usage: "GenAI API key",
},
&cli.StringFlag{
Name: "genai_model",
EnvVars: []string{"MICRO_GENAI_MODEL"},
Usage: "GenAI model to use (optional)",
},
} }
DefaultBrokers = map[string]func(...broker.Option) broker.Broker{ DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
@ -295,6 +312,11 @@ var (
"redis": redis.NewRedisCache, "redis": redis.NewRedisCache,
} }
DefaultStreams = map[string]func(...events.Option) (events.Stream, error){} DefaultStreams = map[string]func(...events.Option) (events.Stream, error){}
DefaultGenAI = map[string]func(...genai.Option) genai.GenAI{
"openai": openai.New,
"gemini": gemini.New,
}
) )
func init() { func init() {
@ -367,6 +389,8 @@ func (c *cmd) Options() Options {
} }
func (c *cmd) Before(ctx *cli.Context) error { func (c *cmd) Before(ctx *cli.Context) error {
// Set GenAI provider from flags/env
setGenAIFromFlags(ctx)
// If flags are set then use them otherwise do nothing // If flags are set then use them otherwise do nothing
var serverOpts []server.Option var serverOpts []server.Option
var clientOpts []client.Option var clientOpts []client.Option
@ -799,3 +823,24 @@ func Register(cmds ...*cli.Command) {
return app.Commands[i].Name < app.Commands[j].Name return app.Commands[i].Name < app.Commands[j].Name
}) })
} }
func setGenAIFromFlags(ctx *cli.Context) {
provider := ctx.String("genai")
key := ctx.String("genai_key")
model := ctx.String("genai_model")
switch provider {
case "openai":
if key == "" {
key = os.Getenv("OPENAI_API_KEY")
}
genai.DefaultGenAI = openai.New(genai.WithAPIKey(key), genai.WithModel(model))
case "gemini":
if key == "" {
key = os.Getenv("GEMINI_API_KEY")
}
genai.DefaultGenAI = gemini.New(genai.WithAPIKey(key), genai.WithModel(model))
default:
genai.DefaultGenAI = genai.Default
}
}

View File

@ -8,11 +8,11 @@ import (
"testing" "testing"
"time" "time"
"go-micro.dev/v5/events/natsjs"
nserver "github.com/nats-io/nats-server/v2/server" nserver "github.com/nats-io/nats-server/v2/server"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/test-go/testify/require" "github.com/test-go/testify/require"
"go-micro.dev/v5/events" "go-micro.dev/v5/events"
"go-micro.dev/v5/events/natsjs"
) )
type Payload struct { type Payload struct {

16
genai/default.go Normal file
View File

@ -0,0 +1,16 @@
package genai
import (
"sync"
)
var (
DefaultGenAI GenAI = &noopGenAI{}
defaultOnce sync.Once
)
func SetDefault(g GenAI) {
defaultOnce.Do(func() {
DefaultGenAI = g
})
}

161
genai/gemini/gemini.go Normal file
View File

@ -0,0 +1,161 @@
package gemini
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"go-micro.dev/v5/genai"
)
// gemini implements the GenAI interface using Google Gemini 2.5 API.
type gemini struct {
options genai.Options
}
func New(opts ...genai.Option) genai.GenAI {
var options genai.Options
for _, o := range opts {
o(&options)
}
if options.APIKey == "" {
options.APIKey = os.Getenv("GEMINI_API_KEY")
}
return &gemini{options: options}
}
func (g *gemini) Generate(prompt string, opts ...genai.Option) (*genai.Result, error) {
options := g.options
for _, o := range opts {
o(&options)
}
ctx := context.Background()
res := &genai.Result{Prompt: prompt, Type: options.Type}
endpoint := options.Endpoint
if endpoint == "" {
endpoint = "https://generativelanguage.googleapis.com/v1beta/models/"
}
var url string
var body map[string]interface{}
// Determine model to use
var model string
switch options.Type {
case "image":
if options.Model != "" {
model = options.Model
} else {
model = "gemini-2.5-pro-vision"
}
url = endpoint + model + ":generateContent?key=" + options.APIKey
body = map[string]interface{}{
"contents": []map[string]interface{}{
{"parts": []map[string]string{{"text": prompt}}},
},
}
case "audio":
if options.Model != "" {
model = options.Model
} else {
model = "gemini-2.5-pro"
}
url = endpoint + model + ":generateContent?key=" + options.APIKey
body = map[string]interface{}{
"contents": []map[string]interface{}{
{"parts": []map[string]string{{"text": prompt}}},
},
"response_mime_type": "audio/wav",
}
case "text":
fallthrough
default:
if options.Model != "" {
model = options.Model
} else {
model = "gemini-2.5-pro"
}
url = endpoint + model + ":generateContent?key=" + options.APIKey
body = map[string]interface{}{
"contents": []map[string]interface{}{
{"parts": []map[string]string{{"text": prompt}}},
},
}
}
b, _ := json.Marshal(body)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if options.Type == "audio" {
var result struct {
Candidates []struct {
Content struct {
Parts []struct {
InlineData struct {
Data []byte `json:"data"`
} `json:"inline_data"`
} `json:"parts"`
} `json:"content"`
} `json:"candidates"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Candidates) == 0 || len(result.Candidates[0].Content.Parts) == 0 {
return nil, fmt.Errorf("no audio returned")
}
res.Data = result.Candidates[0].Content.Parts[0].InlineData.Data
return res, nil
}
var result struct {
Candidates []struct {
Content struct {
Parts []struct {
Text string `json:"text"`
} `json:"parts"`
} `json:"content"`
} `json:"candidates"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Candidates) == 0 || len(result.Candidates[0].Content.Parts) == 0 {
return nil, fmt.Errorf("no candidates returned")
}
res.Text = result.Candidates[0].Content.Parts[0].Text
return res, nil
}
func (g *gemini) Stream(prompt string, opts ...genai.Option) (*genai.Stream, error) {
results := make(chan *genai.Result)
go func() {
defer close(results)
res, err := g.Generate(prompt, opts...)
if err != nil {
// Send error via Stream.Err, not channel
return
}
results <- res
}()
return &genai.Stream{Results: results}, nil
}
func init() {
genai.Register("gemini", New())
}

53
genai/genai.go Normal file
View File

@ -0,0 +1,53 @@
// Package genai provides a generic interface for generative AI providers.
package genai
// Result is the unified response from GenAI providers.
type Result struct {
Prompt string
Type string
Data []byte // for audio/image binary data
Text string // for text or image URL
}
// Stream represents a streaming response from a GenAI provider.
type Stream struct {
Results <-chan *Result
Err error
// You can add fields for cancellation, errors, etc. if needed
}
// GenAI is the generic interface for generative AI providers.
type GenAI interface {
Generate(prompt string, opts ...Option) (*Result, error)
Stream(prompt string, opts ...Option) (*Stream, error)
}
// Option is a functional option for configuring providers.
type Option func(*Options)
// Options holds configuration for providers.
type Options struct {
APIKey string
Endpoint string
Type string // "text", "image", "audio", etc.
Model string // model name, e.g. "gemini-2.5-pro"
// Add more fields as needed
}
// Option functions for generation type
func Text(o *Options) { o.Type = "text" }
func Image(o *Options) { o.Type = "image" }
func Audio(o *Options) { o.Type = "audio" }
// Provider registry
var providers = make(map[string]GenAI)
// Register a GenAI provider by name.
func Register(name string, provider GenAI) {
providers[name] = provider
}
// Get a GenAI provider by name.
func Get(name string) GenAI {
return providers[name]
}

16
genai/noop.go Normal file
View File

@ -0,0 +1,16 @@
package genai
type noopGenAI struct{}
func (n *noopGenAI) Generate(prompt string, opts ...Option) (*Result, error) {
return &Result{Prompt: prompt, Type: "noop", Text: "noop response"}, nil
}
func (n *noopGenAI) Stream(prompt string, opts ...Option) (*Stream, error) {
results := make(chan *Result, 1)
results <- &Result{Prompt: prompt, Type: "noop", Text: "noop response"}
close(results)
return &Stream{Results: results}, nil
}
var Default = &noopGenAI{}

151
genai/openai/openai.go Normal file
View File

@ -0,0 +1,151 @@
package openai
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"go-micro.dev/v5/genai"
)
type openAI struct {
options genai.Options
}
func New(opts ...genai.Option) genai.GenAI {
var options genai.Options
for _, o := range opts {
o(&options)
}
if options.APIKey == "" {
options.APIKey = os.Getenv("OPENAI_API_KEY")
}
return &openAI{options: options}
}
func (o *openAI) Generate(prompt string, opts ...genai.Option) (*genai.Result, error) {
options := o.options
for _, opt := range opts {
opt(&options)
}
res := &genai.Result{Prompt: prompt, Type: options.Type}
var url string
var body map[string]interface{}
switch options.Type {
case "image":
model := options.Model
if model == "" {
model = "dall-e-3"
}
url = "https://api.openai.com/v1/images/generations"
body = map[string]interface{}{
"prompt": prompt,
"n": 1,
"size": "1024x1024",
"model": model,
}
case "audio":
model := options.Model
if model == "" {
model = "tts-1"
}
url = "https://api.openai.com/v1/audio/speech"
body = map[string]interface{}{
"model": model,
"input": prompt,
"voice": "alloy", // or another supported voice
}
case "text":
fallthrough
default:
model := options.Model
if model == "" {
model = "gpt-3.5-turbo"
}
url = "https://api.openai.com/v1/chat/completions"
body = map[string]interface{}{
"model": model,
"messages": []map[string]string{{"role": "user", "content": prompt}},
}
}
b, _ := json.Marshal(body)
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+options.APIKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch options.Type {
case "image":
var result struct {
Data []struct {
URL string `json:"url"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Data) == 0 {
return nil, fmt.Errorf("no image returned")
}
res.Text = result.Data[0].URL
return res, nil
case "audio":
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
res.Data = data
return res, nil
case "text":
fallthrough
default:
var result struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if len(result.Choices) == 0 {
return nil, fmt.Errorf("no choices returned")
}
res.Text = result.Choices[0].Message.Content
return res, nil
}
}
func (o *openAI) Stream(prompt string, opts ...genai.Option) (*genai.Stream, error) {
results := make(chan *genai.Result)
go func() {
defer close(results)
res, err := o.Generate(prompt, opts...)
if err != nil {
// Send error via Stream.Err, not channel
return
}
results <- res
}()
return &genai.Stream{Results: results}, nil
}
func init() {
genai.Register("openai", New())
}

View File

@ -0,0 +1,37 @@
package openai
import (
"go-micro.dev/v5/genai"
"os"
"testing"
)
func TestOpenAI_GenerateText(t *testing.T) {
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
t.Skip("OPENAI_API_KEY not set")
}
client := New(genai.WithAPIKey(apiKey))
res, err := client.Generate("Say hello world", genai.Text)
if err != nil {
t.Fatalf("Generate error: %v", err)
}
if res == nil || res.Text == "" {
t.Error("Expected non-empty text response")
}
}
func TestOpenAI_GenerateImage(t *testing.T) {
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
t.Skip("OPENAI_API_KEY not set")
}
client := New(genai.WithAPIKey(apiKey))
res, err := client.Generate("A cat wearing sunglasses", genai.Image)
if err != nil {
t.Fatalf("Generate error: %v", err)
}
if res == nil || res.Text == "" {
t.Error("Expected non-empty image URL")
}
}

20
genai/options.go Normal file
View File

@ -0,0 +1,20 @@
package genai
// Option sets options for a GenAI provider.
func WithAPIKey(key string) Option {
return func(o *Options) {
o.APIKey = key
}
}
func WithEndpoint(endpoint string) Option {
return func(o *Options) {
o.Endpoint = endpoint
}
}
func WithModel(model string) Option {
return func(o *Options) {
o.Model = model
}
}

11
go.mod
View File

@ -40,12 +40,16 @@ require (
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.37.0
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/sync v0.13.0 golang.org/x/sync v0.13.0
google.golang.org/genai v1.12.0
google.golang.org/grpc v1.71.1 google.golang.org/grpc v1.71.1
google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057 google.golang.org/grpc/examples v0.0.0-20250515150734-f2d3e11f3057
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.36.6
) )
require ( require (
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
@ -55,10 +59,16 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect github.com/google/go-tpm v0.9.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect
@ -91,6 +101,7 @@ require (
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect

20
go.sum
View File

@ -1,3 +1,9 @@
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -55,6 +61,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -95,8 +103,16 @@ github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
@ -355,6 +371,8 @@ go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU= go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 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/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
@ -492,6 +510,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genai v1.12.0 h1:0JjAdwvEAha9ZpPH5hL6dVG8bpMnRbAMCgv2f2LDnz4=
google.golang.org/genai v1.12.0/go.mod h1:HFXR1zT3LCdLxd/NW6IOSCczOYyRAxwaShvYbgPSeVw=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=

View File

@ -43,6 +43,7 @@ type natsStore struct {
js nats.JetStreamContext js nats.JetStreamContext
buckets *hashmap.Map[string, nats.KeyValue] buckets *hashmap.Map[string, nats.KeyValue]
} }
// NewStore will create a new NATS JetStream Object Store. // NewStore will create a new NATS JetStream Object Store.
func NewStore(opts ...store.Option) store.Store { func NewStore(opts ...store.Option) store.Store {
options := store.Options{ options := store.Options{

View File

@ -198,11 +198,11 @@ func (s *sqlStore) List(opts ...store.ListOption) ([]string, error) {
var rows pgx.Rows var rows pgx.Rows
if options.Limit > 0 { if options.Limit > 0 {
rows, err = db.Query(s.options.Context, queries.ListAscLimit, pattern, options.Limit, options.Offset) rows, err = db.Query(s.options.Context, queries.ListAscLimit, pattern, options.Limit, options.Offset)
} else { } else {
rows, err = db.Query(s.options.Context, queries.ListAsc, pattern) rows, err = db.Query(s.options.Context, queries.ListAsc, pattern)
} }
if err != nil { if err != nil {
@ -273,9 +273,7 @@ func (s *sqlStore) rowsToRecords(rows pgx.Rows) ([]*store.Record, error) {
// Read a single key // Read a single key
func (s *sqlStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) { func (s *sqlStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
options := store.ReadOptions{ options := store.ReadOptions{}
}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@ -307,11 +305,11 @@ func (s *sqlStore) Read(key string, opts ...store.ReadOption) ([]*store.Record,
var rows pgx.Rows var rows pgx.Rows
if options.Limit > 0 { if options.Limit > 0 {
rows, err = db.Query(s.options.Context, queries.ListAscLimit, pattern, options.Limit, options.Offset) rows, err = db.Query(s.options.Context, queries.ListAscLimit, pattern, options.Limit, options.Offset)
} else { } else {
rows, err = db.Query(s.options.Context, queries.ListAsc, pattern) rows, err = db.Query(s.options.Context, queries.ListAsc, pattern)
} }
if err != nil { if err != nil {

View File

@ -257,8 +257,6 @@ func (s *sqlStore) prepare(database, table, query string) (*sql.Stmt, error) {
return nil, errors.New("unsupported statement") return nil, errors.New("unsupported statement")
} }
// get DB // get DB
database, table = s.getDB(database, table) database, table = s.getDB(database, table)

View File

@ -61,8 +61,6 @@ var (
DefaultTimeout = time.Minute DefaultTimeout = time.Minute
) )
func configure(n *ntport, opts ...transport.Option) { func configure(n *ntport, opts ...transport.Option) {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)