Go
The official ShipSilently client for Go. Generic evaluation, first-class
context.Context support, SSE streaming with automatic
reconnection, and a last-known-good cache.
shipsilently/shipsilently-go Install
go get github.com/shipsilently/shipsilently-go Quick start
package main
import (
"context"
"fmt"
"os"
shipsilently "github.com/shipsilently/shipsilently-go"
)
func main() {
client := shipsilently.New(shipsilently.Config{
APIKey: os.Getenv("SHIPSILENTLY_KEY"),
// APIURL defaults to https://api.shipsilently.com
})
ctx := context.Background()
userCtx := shipsilently.UserContext{
"userId": "u_123",
"plan": "pro",
"country": "US",
}
// Evaluate is a generic package-level function, not a method — Go does not
// allow type parameters on methods. The default value fixes T, so
// useNewFlow is a bool and variant is a string, with no casting.
useNewFlow, err := shipsilently.Evaluate(ctx, client, "new-checkout-v2", userCtx, false)
if err != nil {
// err is non-nil on a failed lookup, but the returned value is still
// safe to use: it is the last-known-good cached value, or your default.
fmt.Fprintf(os.Stderr, "flag lookup degraded: %v\n", err)
}
variant, _ := shipsilently.Evaluate(ctx, client, "hero-variant", userCtx, "control")
fmt.Println(useNewFlow, variant)
} Evaluate is a package-level function, not a method.
Go does not allow type parameters on methods, so generic evaluation has to
take the client as an argument:
shipsilently.Evaluate(ctx, client, key, userCtx, default). The
default value fixes T, so there is no casting.
Errors and the cache
Evaluate returns (value, error), and the value is
always safe to use. On a failed request the client serves the last-known-good
cached value with a nil error. A non-nil error therefore
means there was no cached value either, and what you got back is the default
you passed.
enabled, err := shipsilently.Evaluate(ctx, client, "new-checkout", userCtx, false)
if err != nil {
// No cached value was available — 'enabled' is the compile-time default.
// Usually worth a metric, rarely worth failing the request.
log.Printf("shipsilently degraded: %v", err)
}
// Use 'enabled' either way.
Set DisableCache: true to opt out and have every failure surface
an error with the compile-time default. See
Resilience for the full failure matrix.
Evaluate all flags & stream
EvaluateAll fetches every flag in one call.
Stream keeps them fresh over SSE and returns a cancel function.
package main
import (
"context"
"log"
"os"
"time"
shipsilently "github.com/shipsilently/shipsilently-go"
)
func main() {
client := shipsilently.New(shipsilently.Config{
APIKey: os.Getenv("SHIPSILENTLY_KEY"),
// Reconnection backoff. Zero values use the defaults shown here.
Retry: shipsilently.RetryConfig{
BaseDelay: 1 * time.Second,
MaxDelay: 60 * time.Second,
HeartbeatTimeout: 90 * time.Second,
},
// Refresh cadence when streaming is plan-gated (HTTP 402).
PollingInterval: 30 * time.Second,
})
ctx, stop := context.WithCancel(context.Background())
defer stop()
// Fetch everything once for the process-level context.
all, err := client.EvaluateAll(ctx, shipsilently.UserContext{"userId": "system"})
if err != nil {
log.Printf("initial load degraded: %v", err)
}
log.Printf("loaded %d flags", len(all))
// Then keep it fresh. cancel() closes the stream.
cancel, err := client.Stream(ctx, shipsilently.UserContext{"userId": "system"},
func(flags map[string]any) {
log.Printf("flags refreshed: %d", len(flags))
})
if err != nil {
log.Fatalf("stream: %v", err)
}
defer cancel()
select {}
} Configuration
| Field | Type | Default | Notes |
|---|---|---|---|
APIKey | string | — | Required. Environment-scoped key. |
APIURL | string | https://api.shipsilently.com | Override for self-hosted deployments. |
HTTP | *http.Client | 5s timeout | Bring your own transport, proxy, or tracing. |
Retry | RetryConfig | 1s / 60s / 90s | Base delay, max delay, heartbeat timeout. |
PollingInterval | time.Duration | 30s | Cadence when streaming is plan-gated (HTTP 402). |
DisableCache | bool | false | Turn off last-known-good caching. |
Logger | *log.Logger | log.Default() | Receives connection warnings, deduplicated to one per minute per class. |
API reference
| Signature | Notes |
|---|---|
New(cfg Config) *Client | Construct once and share; safe for concurrent use. |
Evaluate[T](ctx, c, key, userCtx, default) (T, error) | Package-level generic function. |
(*Client) EvaluateAll(ctx, userCtx) (map[string]any, error) | Every flag in one call. |
(*Client) Stream(ctx, userCtx, handler) (context.CancelFunc, error) | Call the returned func to close the stream. |
Types
// UserContext holds attributes used for flag targeting.
type UserContext map[string]any
// StreamHandler is called with the full flag map on every update.
type StreamHandler func(flags map[string]any) *Client is safe for concurrent
use, its cache is guarded by a read-write mutex. Create one per process, not
per request.