Features
15 packages across three tiers. Each package stands alone. Together they are Glacier. The tiers enforce a no-cycles constraint: kernel packages have no internal dependencies; mid-tier packages depend only on the kernel; leaf packages depend only on kernel and mid-tier, never on each other. See Concepts for the full mental model.
Tier 0: Kernel
The five kernel packages are universal. Every consumer of any Glacier package transitively depends on these. They are small, fast, and stable by design.
option Kernel · Tier 0
Functional options. Apply, Validate, Required, Option[T]: every Glacier package configurable at construction speaks this protocol. option.OptionFunc[T] adapts a closure into an Option[T] without boilerplate. option.Apply applies a slice of options to a zero-valued config struct, stopping at first error by default or collecting all errors with Strict() mode.
func WithTimeout(d time.Duration) option.Option[clientConfig] {
return option.OptionFunc[clientConfig](func(c *clientConfig) error {
if d <= 0 {
return errors.New("httpc: WithTimeout: duration must be positive")
}
c.timeout = d
return nil
})
}errs Kernel · Tier 0
Error stories without ceremony. Wrap, Join, Chain, Sentinel, IsAny, Retryable, Coded: tree-walking, classification, library and CLI registers. errs.Wrap attaches a prefix and optional stack trace. errs.Chain produces a lazy iter.Seq[error] for depth-first error-tree traversal. errs.Sentinel panics at construction time if the error message violates the library register, keeping error format discipline automatic.
var ErrNotFound = errs.Sentinel("store: record not found")
func (s *Store) Get(ctx context.Context, id string) (*Record, error) {
r, err := s.db.Query(ctx, id)
if err != nil {
return nil, errs.Wrap(err, "store: get")
}
return r, nil
}log Kernel · Tier 0
Structured logging on log/slog with Trace and Notice levels, brand-palette TTY color, context attribute attachment, and a Redact helper. log.NewHandler and log.NewJSONHandler construct slog.Handler implementations with Glacier's level set and color support. log.With(ctx, attrs...) attaches attributes to the context so every downstream log call includes them without threading a logger.
ctx = log.With(ctx,
slog.String("request_id", reqID),
slog.String("user_id", userID),
)
// All log calls below automatically include request_id and user_id.
log.From(ctx).Info("handling request", slog.String("path", r.URL.Path))assert Kernel · Tier 0
Test assertions and runtime invariants. Equal[T] with smart deep-compare, Must for init-time panics, require/ for halt-on-failure. assert.Equal accepts option modifiers: IgnoreOrder(), IgnoreCase(), WithDelta(d), IgnoreFields(names...). The require sub-package mirrors every assertion with t.FailNow() semantics for cases where continuing after a failure is misleading.
assert.Equal(t, wantUsers, gotUsers,
assert.IgnoreOrder(),
assert.IgnoreFields("UpdatedAt"),
)
assert.NoError(t, err)
assert.Match(t, `^user:\d+$`, got.ID)term Kernel · Tier 0
Terminal as first-class output. Capability detection, 24-bit ANSI styling, glyph registry, beauty-writer layout, prompts, animation. term.Detect() reads COLORTERM, TERM, NO_COLOR, GLACIER_NO_COLOR and resolves the right color mode. term.Style wraps a string with the correct ANSI SGR escape for a given palette token. The beauty-writer handles truncation, padding, and column alignment for structured terminal output.
w := term.NewWriter(os.Stdout, term.WithColor(term.ColorAuto))
w.Println(term.Style("Ready", term.TokenSuccess))
w.Printf("Listening on :%s\n", port)Tier 1: Mid
The five mid-tier packages are independent of each other. Each depends only on the kernel. Pick one, pick all five - you won't drag in unrelated packages.
concur Mid · Tier 1
Concurrency primitives that play with context: Mutex, RWMutex, Group with panic recovery, Semaphore, Pool[T], Once[T], WaitGroup. concur.Group runs goroutines and recovers panics, surfacing them as errors rather than crashing the process. concur.Mutex.LockCtx(ctx) acquires with cancellation support - useful when a lock contention could outlast a request deadline.
g := concur.NewGroup(ctx)
g.Go(func(ctx context.Context) error {
return processChunk(ctx, chunk)
})
if err := g.Wait(); err != nil {
return err
}fluent Mid · Tier 1
Lazy iter.Seq pipeline operators: Map, Filter, Take, Window, GroupBy, joins, set ops, aggregations. Generics-first; zero deps. Sequences are lazy by default - fluent.Of wraps any iter.Seq[T], and operators chain without allocating intermediate slices. Evaluation happens only when a sink (Collect, First, Reduce) pulls from the sequence.
results := fluent.Collect(
fluent.Take(
fluent.Filter(
fluent.Map(rows, parseRow),
func(r Row) bool { return r.Active },
),
100,
),
)conf Mid · Tier 1
Layered configuration with atomic snapshots. Defaults, JSON file, env, flags, overrides: Register[T] returns a typed accessor. Layers are applied in priority order (defaults lowest, explicit overrides highest). The registry stores an atomic snapshot; reads never block writers. conf.Register[T] binds a typed key - the returned accessor func always returns the current snapshot value.
loader, _ := conf.New(
conf.WithFile("config.json"),
conf.WithEnvPrefix("APP"),
conf.WithFlags(flag.CommandLine),
)
logLevel := conf.Register[string](loader, "log.level",
conf.WithDefault("info"),
)
fmt.Println(logLevel()) // "debug" if APP_LOG_LEVEL=debug in envfixture Mid · Tier 1
Test resources: golden files, typed snapshots, deterministic fake clocks, in-memory filesystems, leak guards for goroutines, FDs, env vars. fixture.Golden reads a .golden file from testdata/ and diffs against it, auto-updating with -update. fixture.Clock returns a fake time.Time source that advances on demand. fixture.GuardLeaks asserts no goroutine, file descriptor, or environment variable outlives the test.
func TestRenderReport(t *testing.T) {
got := renderReport(fakeData())
fixture.Golden(t, "report", got) // testdata/TestRenderReport/report.golden
}obs Mid · Tier 1
Opt-in OpenTelemetry: MeterProvider and TracerProvider via OTLP gRPC, instrumentation hooks for httpc, cli, conf, zero overhead when off. obs.New constructs a provider pair. When the context carries no tracer, span creation is a no-op with no allocation. Instrument hooks attach to httpc, cli, and conf via the standard option.Option[T] mechanism - no global state involved.
provider, _ := obs.New(ctx,
obs.WithOTLPEndpoint("otelcol:4317"),
obs.WithServiceName("my-service"),
)
defer provider.Shutdown(ctx)
ctx, span := provider.Tracer("my-service").Start(ctx, "handle-request")
defer span.End()Tier 2: Leaves
The five leaf packages are large enough to justify isolation. They depend on kernel and mid-tier packages only and never import each other. Consumers who need only httpmock for tests don't pull in cli.
cli Leaf · Tier 2
Build CLIs from a struct and a Run method. Comment markers and glaciergen codegen emit flag parsing, help text, and routing. The +glacier:command marker on a struct generates the wiring code at go generate time. Per-field markers like +glacier:default, +glacier:env, +glacier:short, and +glacier:required configure each flag. The banner feature embeds assets/logo/wordmark.txt via //go:embed and renders it with the 6-stop ice gradient on TTY output.
// +glacier:command name=serve
// +glacier:root
type Serve struct {
// +glacier:default 8080
// +glacier:short p
Port string
// +glacier:env ENABLE_METRICS
Metrics bool
}
func (s *Serve) Run(ctx context.Context) error {
return listenAndServe(ctx, ":"+s.Port, s.Metrics)
}mock Leaf · Tier 2
Interface mocks: mock.Of[T] reflect-based, or +glacier:mock codegen for typed wrappers. Fluent expectation builder, automatic Verify on cleanup. mock.Of[T] returns a value satisfying interface T backed by a recorder. mock.Expect(m, "Method").With(...).Return(...) registers an expectation. t.Cleanup triggers Verify automatically - no explicit call needed in tests.
db := mock.Of[Database](t)
mock.Expect(db, "GetUser").
With(mock.AnyContext(), "user-42").
Return(&User{ID: "user-42"}, nil)
svc := NewService(db)
u, err := svc.FindUser(t.Context(), "user-42")
assert.NoError(t, err)
assert.Equal(t, "user-42", u.ID)httpmock Leaf · Tier 2
A programmable http.RoundTripper for tests. Stub builder, generic JSON[T] responses, strict-by-default; testdata fixtures land in JSON. httpmock.New() returns a Transport that serves registered stubs in order. Strict mode (the default) fails the test if a request arrives with no matching stub. httpmock.JSON[T](200, v) serializes v and sets the correct Content-Type header.
transport, _ := httpmock.New(httpmock.Strict())
httpmock.Register(transport,
httpmock.GET("/api/users/1").
RespondWith(httpmock.JSON(200, User{ID: "1", Name: "Alice"})),
)
client := &http.Client{Transport: transport}
resp, _ := client.Get("https://api.example.com/api/users/1")httpc Leaf · Tier 2
Typed HTTP client: Get[T], Post[T], Put[T] auto-unmarshal. Retry with backoff, closure-body retry-safe payloads, dry-run via context. httpc.Get[T](ctx, url) fetches and unmarshals into T in one call. The retry loop re-invokes the body closure on each attempt so the request body is always readable. Dry-run mode (injected via context) records calls without making real network requests.
type User struct { ID string; Name string }
user, err := httpc.Get[User](ctx, "https://api.example.com/users/1",
httpc.WithRetry(3, httpc.ExponentialBackoff(100*time.Millisecond)),
)
if err != nil {
return err
}
fmt.Println(user.Name) // Alicecache Leaf · Tier 2
Generic key-value cache with TTL. One Cache[V] interface; three implementations: New[V] in-memory (map+RWMutex, zero-alloc on hit), NewDisk[V] per-key JSON files with advisory flock for cross-process safety, and NewLayered[V] write-through composition. GetOrLoad collapses concurrent misses on the same key onto a single loader call via singleflight. Tests in dependent packages mock Cache[V] instead of stubbing files. Hit/miss counters surface via obs when an OTLP endpoint is configured; zero overhead otherwise.
mem := cache.New[Release](cache.WithDefaultTTL(24 * time.Hour))
disk, err := cache.NewDisk[Release](filepath.Join(cacheDir, "glacier"))
if err != nil {
return err
}
c := cache.NewLayered(mem, disk)
release, err := c.GetOrLoad(ctx, "github:nathanbrophy/glacier",
func(ctx context.Context) (Release, error) {
return fetcher.Latest(ctx, "nathanbrophy/glacier")
})