https://github.com/7c/appswitch-go
appswitch golang client
https://github.com/7c/appswitch-go
Last synced: about 1 month ago
JSON representation
appswitch golang client
- Host: GitHub
- URL: https://github.com/7c/appswitch-go
- Owner: 7c
- Created: 2026-05-21T14:00:02.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-23T12:04:49.000Z (about 1 month ago)
- Last Synced: 2026-05-23T14:07:21.053Z (about 1 month ago)
- Language: Go
- Size: 24.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# appswitch-go
Official Go client SDK for **appswitch** — typed, cached, read-only config access.
- **Read once, serve forever** — after `Start`, reads are answered from an in-memory snapshot.
- **Caching on by default** — in-memory always; SDK-owned disk cache, unlimited TTL.
- **Resilient** — keeps serving the last good snapshot when a refresh fails; per-call fallback.
- **Typed accessors** — `Number`, `Bool`, `URL`, `Semver`, `ArrayString`, `Enum`, `JSON(&out)`, … each `(T, error)`.
- **Change hooks** — whole-config, section/glob, and per-key, fired on the polling boundary.
- **Best-effort telemetry** — batched read counters flushed to `/v1/_stats`.
Read-only by design: no management, no writes, no realtime push (clients poll). See
[`docs/CLIENT.md`](../../docs/CLIENT.md) for the full concept. Stdlib-only; Go ≥ 1.24.
## Install
```bash
go get github.com/7c/appswitch-go
```
## Usage
```go
import (
"context"
"log"
"os"
"time"
appswitch "github.com/7c/appswitch-go"
)
client, err := appswitch.New(appswitch.Config{
Name: "checkout-api", // required
Version: "3.2.1", // required
APIKey: os.Getenv("APPSWITCH_KEY"),
Endpoint: "https://api.appswitch.example.com",
PollInterval: 5 * time.Minute, // 0 = default 5m; negative = disabled
CacheTTL: 0, // 0 = unlimited (default)
StaleIfError: appswitch.Bool(true), // default true
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
port, _ := client.Number("main.lockport") // 8443
theme, _ := client.Enum("features.checkout.theme") // "dark"
minVer, _ := client.Semver("mobile.minSupportedVersion") // "3.4.0"
if v, err := client.SemverObject("mobile.minSupportedVersion"); err == nil && v.Lt(appswitch.MustParseSemver("4.0.0")) {
// force upgrade
}
var shipping struct {
Flat float64 `json:"flat"`
Regions []string `json:"regions"`
}
_ = client.JSON("features.checkout.shippingMatrix", &shipping)
// handles
client.Key("main.lockport").OnChange(func(e appswitch.Event) {
log.Printf("port %v -> %v", e.Previous, e.Current)
})
client.Section("billing").OnChange(func(e appswitch.Event) {
log.Printf("billing changed: %s", e.Path)
})
```
## Surface
| Concept | Method |
|---|---|
| Construct | `appswitch.New(cfg)` |
| Lifecycle | `Start(ctx)`, `Stop()`, `<-Ready()` |
| Read | `Get(path, fallback...)`, `Raw(path, fallback...)` |
| Typed read | `Number` · `String` · `Bool` · `URL` · `Datetime` · `Interval` · `Semver` · `SemverObject` · `ArrayString` · `ArrayNumber` · `Enum` · `JSON(path, &out)` |
| Semver helpers | `ParseSemver`, `CompareSemver`, `IsSemver` — package-level; `(*Semver).Compare`, `.Lt`/`.Gt`, `.Bump` |
| Snapshot | `Snapshot()`, `LastFetch()` |
| Refresh | `Refresh(ctx)` |
| Handles | `Key(path)` → `*AppSwitchKey`, `Section(prefix)` → `*AppSwitchSection` |
| Hooks | `OnChange`, `OnSection`, `OnNew`, `OnModify`, `OnRemove` |
Typed accessors return `(T, error)`. A trailing variadic `fallback` is returned for availability
errors (not ready / not found / network / stale); type mismatches always error. Inspect errors with
`appswitch.CodeOf(err)` or `errors.As(err, &ae)` for an `*appswitch.Error`.
## Config
| Field | Type | Default |
|---|---|---|
| `Name` / `Version` / `APIKey` | string | **required** |
| `Endpoint` | string | `https://api.appswitch.example.com` |
| `CacheTTL` | `time.Duration` | `0` = unlimited |
| `PollInterval` | `time.Duration` | `0` → 5m; negative → disabled |
| `MaxStaleness` | `time.Duration` | `0` = infinite |
| `NegativeCacheTTL` | `time.Duration` | 5m |
| `TelemetryFlushInterval` | `time.Duration` | `0` → 60s; negative → disabled |
| `RequestTimeout` | `time.Duration` | 5s |
| `CacheFolder` / `DisableDiskCache` | string / bool | OS cache dir; `DisableDiskCache` for in-memory only |
| `StaleIfError` | `*bool` (use `appswitch.Bool`) | `true` |
| `UserAgent` | string | `appswitch-go/ /` |
| `OnError` | `func(error)` | no-op |
The client is safe for concurrent use and verified with `go test -race`.