Skip to content
Zever
Home

API index

Package entry points with container accessors for every service.

4 min readEdit on GitHub

Battery interfaces live in core/<b> with the Register/Open registry, each adapter lives in its own module at adapters/<b>/<a> with a Register() call wiring it in, and SDK-free helpers live in shared/*. Import the core interface plus the adapter modules you need and resolve through container. The dsl/* packages are internal-only: runtime code must not import the compiler.

Construction is always config plus container. Each service resolves on first use and caches the result; on error the cached state clears so the next call retries.

import (
"github.com/zenta-dev/zever/config"
"github.com/zenta-dev/zever/container"
)
c := container.New(config.Default())
db, err := c.DB()
if err != nil {
return err
}
q, err := c.Queue()
if err != nil {
return err
}
d, err := c.Job() // d shares the resolved queue instance
if err != nil {
return err
}

container.New(nil) is valid: an untouched container closes cleanly without opening anything. Job() has no registry of its own; it builds a *job.Dispatcher over the already-resolved Queue. Scheduler() injects that dispatcher, so the scheduler shares the same Queue connection instead of opening a redundant one. Resolving Scheduler() therefore resolves Job() and, transitively, Queue as a side effect.

GRPCClient(target, opts...) builds *grpc.ClientConn connections through the shared/grpcclient helper (round-robin LB, retry policy, TLS, static resolver) and caches one conn per target string; the conns close with the container.

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := c.Close(ctx); err != nil {
var te *container.CloseTimeoutError
var pe *container.ClosePanicError
_ = te
_ = pe
return err
}

Close shuts down only resolved services, dependents first, joining per-service close failures via errors.Join.

Package Role
config layered typed config, defaults/file/env (own module)
container sole resolver, lazy accessors, ordered close (own module)
orm generics typed builder over core/db (own module)
dsl .zen compiler frontend, compile time only (own module)
core/middleware shared HTTP middleware (own module)
cmd/zever flags-only toolkit shell (own module)
shared/* SDK-free helpers (codec, redisclient, redisopt, s3opts, retry, lrucache, …), one module each
shared/grpcclient gRPC client constructor (New(ctx, target, opts...): LB, retry, TLS, static resolver); no registry, resolved via GRPCClient(target, opts...)
Package Accessor Notes
core/agent Agent() *agent.Loop
core/ai AI()
core/analytics Analytics()
core/auth Auth()
core/billing Billing()
core/cache Cache() leaf dependency, closed last
core/cdn CDN()
core/crypto Crypto()
core/db DB() plus Transactor() helper
core/document Document()
core/eventbus EventBus() (Eventbus() alias)
core/flag Flag()
core/geo Geo()
grpc GRPC(opts...) lazy *grpc.Server singleton, no registry
core/i18n I18n()
core/idempotency Idempotency()
core/job Job() *job.Dispatcher over resolved Queue
core/lock Lock()
core/log Log()
core/mailer Mailer()
core/media Media()
core/notification Notification()
core/observability Observability()
core/outbox Outbox()
core/password Password()
core/payment Payment()
core/permission Permission()
core/queue Queue() leaf dependency, closed last
core/rag RAG() *rag.Engine
core/ratelimit RateLimit() (Ratelimit() alias)
core/resilience Resilience()
core/router Router()
core/scheduler Scheduler() shares Queue via Job(), closed first
core/search Search()
core/secrets Secrets()
core/session Session()
core/storage Storage()
core/tenant Tenant()
core/vectorstore VectorStore()
core/webhook Webhook()
core/workflow Workflow()
Shape Used by Semantics
Close(ctx) pooled or network-backed services that may block (db, cache, storage, lock, container itself) context-bounded shutdown
Close() lightweight handles, iterators, statements, instant-close in-memory adapters (auth, queue, session, payment, eventbus.Pusher, ratelimit.Limiter, db.Rows, db.Stmt) instant close
Stop() scheduler only cron lifecycle stop

Probing (closeAny) tries Close(context.Context) error, then Close() error, then Stop() error, then Shutdown(context.Context) error (observability providers flush buffered spans/metrics), then no-op nil (*job.Dispatcher has no shutdown method). Crypto, log, password, permission, and router declare no Close/Stop/Shutdown and probe as no-op nil. Callers never guess: the probe picks the shape.

Close order:

scheduler, job dependents holding a queue ref, first
snapshots everything without explicit ordering
cache, queue leaf dependencies, last
db, registry pools shared pools, after every borrower (no use-after-close)
grpcServer independent GracefulStop/Stop, bounded by ctx
grpc clients independent, one conn per target, closed with the container

Per-service bound is 5s only when the parent context carries no deadline; otherwise the parent deadline bounds each wait. Shared instances close once (pointer-identity dedup with a keepAlive set for the duration). Failures join via errors.Join; panics become *ClosePanicError, timeouts become *CloseTimeoutError. Error values name the service only, never resolved values. Close never opens anything: untouched services stay untouched.

  • Start from config.Default() + container.New, then per-service accessors; no globals, no init() wiring.
  • Keep to documented interfaces; new behavior means new interfaces, never new methods on public ones.
  • Pass a parent deadline to Close in servers so shutdown is bounded end to end; inspect CloseTimeoutError / ClosePanicError with errors.As.
  • Goroutine-safe types document their exit path; resolve the in-memory subset and Close it in tests so goleak stays clean.
  • Close hangs: parent context has no deadline and a service blocks; pass a timeout context (per-service bound is 5s only without one).
  • Close reports CloseTimeoutError: a service outlived its bound. Check which service is named, then its backing store health.
  • Close reports ClosePanicError: a service panicked during shutdown. Fix the service; the panic value is captured, not re-thrown.
  • Accessor keeps failing: cached state clears on error, so each call retries fresh. Persistent failure means config or backend, not the container; check Validate and connectivity.