gas

package module
v0.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 28 Imported by: 0

README

Gas

Test Go Reference Go Version License

Gas is a modular Go framework for building micro-SaaS applications. It provides the plumbing every product needs (dependency injection, routing, middleware, events, migrations, and service lifecycle management) so you can spend your time on business logic instead of rebuilding the same wiring for each project.

📚 Documentation · Getting started · Guides · API reference

Quick start

go get github.com/gasmod/gas
package main

import (
	"log"
	"net/http"

	"github.com/gasmod/gas"
)

func main() {
	app := gas.NewApp()

	app.Router().Handle("", http.MethodGet, "/", func(ctx gas.Context) error {
		return ctx.Text(http.StatusOK, "Hello, World!")
	})

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}
go run .
# listening on 0.0.0.0:8080

That is a complete Gas app. From there you write services and register modules the same way, letting the DI container wire them together.

Modules

This repository is a monorepo. github.com/gasmod/gas is the core package, and each official module lives beside it as a separate Go module, so go get pulls in only what you use.

Module Import path What it gives you
core github.com/gasmod/gas App and Worker lifecycle, DI container, router, middleware, events, provider interfaces
auth github.com/gasmod/gas/auth JWT, server-side sessions, API keys, and single-use tokens
cache github.com/gasmod/gas/cache Key-value caching, in-memory or Valkey (Redis-compatible)
config github.com/gasmod/gas/config Config from env vars, JSON, .env, and AWS Secrets Manager, bound to structs
database github.com/gasmod/gas/database database/sql and native pgx pools, transaction helpers, sqlc-friendly
email github.com/gasmod/gas/email Transactional email over AWS SES, including templated sends
log github.com/gasmod/gas/log Structured logging (zerolog or slog) and HTTP/OTLP log shipping
migrate github.com/gasmod/gas/migrate Service-owned database migrations with dirty-state detection and rollback
queue github.com/gasmod/gas/queue Job queues backed by AWS SQS
storage github.com/gasmod/gas/storage Object storage on S3 and S3-compatible services
template github.com/gasmod/gas/template Template storage: memory, directory, embedded FS, database, or composite
ui github.com/gasmod/gas/ui HTML rendering with layouts and partials, static files, HTMX fragments

No module imports another module's service package. They meet through provider interfaces declared in core, so any of them can be replaced by your own implementation or a test mock without the others noticing.

Versioning

All modules share a single version and are released together. A release tags the root module vX.Y.Z and each submodule <dir>/vX.Y.Z, which is what go get github.com/gasmod/gas/auth@vX.Y.Z resolves against. Mixing versions across modules is not supported, so upgrade them together.

Gas is pre-1.0: minor versions may contain breaking changes. See the CHANGELOG.

Examples

Five runnable applications live in example/, from a bare hello world up to a full API server using auth, database, storage, queue, email, and cache together.

cd example/hello-world && go run ./cmd

Documentation

Where What
gasmod.github.io/gas Guides, concepts, and getting started
pkg.go.dev API reference for every package
example/ Runnable applications

Documentation lives in one place per kind, on purpose. Guides and concepts are on the site, API reference comes from doc comments on pkg.go.dev, and each module's README is a signpost to both. Code blocks on the site are imported from a Go module that compiles in CI, so they cannot drift from the framework.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for the workflow: conventional commits, signed-off commits (DCO), tests with every change, and make lint.

make test-all   # go test -race in every module
make lint-all   # golangci-lint in every module
make build-all  # go build in every module

A go.work file at the root ties the modules together, so local changes to one module are picked up by the others without a replace directive of your own.

Community and policies

License

MIT © Ahmed Kamal

Documentation

Overview

Package gas provides the core framework: DI container, App/Worker lifecycle, Router with ownership tracking, EventBus, ConfigProvider, and the provider interfaces (Logger, Authenticator, StorageProvider, ...) that other gasmod modules implement.

See the module README for usage examples and design rationale.

SPDX-License-Identifier: MIT

Index

Constants

View Source
const (
	CodeBadRequest       = "bad_request"
	CodeInvalidJSON      = "invalid_json"
	CodeInvalidForm      = "invalid_form"
	CodeUnauthorized     = "unauthorized"
	CodeForbidden        = "forbidden"
	CodeNotFound         = "not_found"
	CodeConflict         = "conflict"
	CodeValidationFailed = "validation_failed"
	CodeRateLimited      = "rate_limited"
	CodeInternal         = "internal_error"
	CodeUnavailable      = "service_unavailable"
)

Error codes written by Gas core. Applications are free to define their own and pass them to NewError.

Variables

View Source
var AppConfigUpdated = Event[AppConfigUpdatedPayload]{Name: "gas:config-updated"}

AppConfigUpdated is emitted when the app config is updated.

View Source
var SystemAllServicesInitialized = Event[SystemAllServicesInitializedPayload]{Name: "gas:all-services-initialized"}

SystemAllServicesInitialized is emitted when all services have been successfully initialized.

View Source
var SystemServerShuttingDown = Event[SystemServerShuttingDownPayload]{Name: "gas:server-shutting-down"}

SystemServerShuttingDown is emitted when the HTTP server is shutting down. For code that should run on any shutdown (not just HTTP), subscribe to SystemShuttingDown instead.

View Source
var SystemServiceClosed = Event[SystemServiceClosedPayload]{Name: "gas:service-closed"}

SystemServiceClosed is emitted when a service is closed at runtime.

View Source
var SystemServiceInitialized = Event[SystemServiceInitializedPayload]{Name: "gas:service-initialized"}

SystemServiceInitialized is emitted when a service is (re-)initialized at runtime.

View Source
var SystemShuttingDown = Event[SystemShuttingDownPayload]{Name: "gas:shutting-down"}

SystemShuttingDown is emitted when a Worker or App begins its shutdown sequence. It fires for both HTTP (App) and non-HTTP (Worker) workloads.

Functions

func ApplyEnqueueOptions

func ApplyEnqueueOptions(opts []EnqueueOption) (delay time.Duration, groupID, dedupeID string, attrs map[string]string)

ApplyEnqueueOptions resolves variadic EnqueueOption values into a concrete enqueueOptions struct. Implementations call this inside their Enqueue method.

func ApplyStorageOptions

func ApplyStorageOptions(opts []StorageOption) (bucket, contentType string, metadata map[string]string)

ApplyStorageOptions resolves variadic StorageOption values into their individual fields. Implementations call this inside their methods.

func CacheControl

func CacheControl(opt ...CacheControlOption) func(next http.Handler) http.Handler

CacheControl is middleware that sets the Cache-Control response header based on path matching rules and configured directives. If no path filters are specified, the header is applied to all requests. If no directives are specified, the middleware passes through without setting any header.

func Emit

func Emit[T any](bus *EventBus, event Event[T], data T) *sync.WaitGroup

Emit dispatches a typed event to all subscribers concurrently.

func MetadataValue

func MetadataValue[T any](m PrincipalMetadata, key string) (T, bool)

MetadataValue is a type-safe helper that retrieves a typed value from PrincipalMetadata. It returns the value and true if the key exists and the type assertion succeeds, or the zero value and false otherwise.

func MustResolve

func MustResolve[T any](r Resolver) T

MustResolve is like Resolve but panics if the service cannot be resolved.

func MustResolveFromRequestScope

func MustResolveFromRequestScope[T any](r *http.Request) T

MustResolveFromRequestScope retrieves a service of type T from the request's Scope and panics if it cannot be resolved.

func RegisterCtor

func RegisterCtor[T any](c *ServiceContainer, ctor any, lifetime ServiceLifetime)

RegisterCtor registers a constructor for type T with an optional lifetime. Constructor signature: func(DepA, DepB, ...) T or func(DepA, DepB, ...) (T, error)

Panics if ctor is not a function the container can call: it must be non-variadic, return one or two values, produce a first result assignable to T (or implementing T when T is an interface), and, when it returns two, have error as the second. See validateCtorShape.

Panics if lifetime is Transient and T implements Service — transient services cannot have managed lifecycles. Use Singleton or Scoped instead.

A type that declares Init or Close but does not fully implement Service is rejected by BuildAll; see validateServiceShape.

func RegisterInstance

func RegisterInstance[T any](c *ServiceContainer, val T)

RegisterInstance registers a pre-built value. Treated as a singleton.

func RequestLogger

func RequestLogger[T Logger](opt ...RequestLoggerOption) func(next http.Handler) http.Handler

RequestLogger is middleware that logs HTTP requests and responses using a scoped Logger resolved from the request's DI scope. It logs method, path, status, bytes written, duration, and remote address. Responses with status >= 400 are logged at error level; all others at info level.

The type parameter T must be the concrete Logger implementation registered in the DI container. If the logger cannot be resolved, the middleware passes through silently.

When appendRequestID is enabled (the default), the middleware expects chi's RequestID middleware to be mounted upstream so that a request ID is available via middleware.GetReqID.

func Resolve

func Resolve[T any](r Resolver) (T, error)

Resolve retrieves or builds a service of type T from a Resolver (either *ServiceContainer or *Scope).

func ResolveFromRequestScope

func ResolveFromRequestScope[T any](r *http.Request) (T, error)

ResolveFromRequestScope retrieves or builds a service of type T from the per-request scope in the provided *http.Request.

func SecurityHeaders

func SecurityHeaders(opt ...SecurityHeadersOption) func(next http.Handler) http.Handler

SecurityHeaders is middleware that sets common security-related HTTP response headers. It applies secure defaults out of the box (nosniff, DENY framing, strict referrer policy, and restrictive permissions policy). Use the functional options to override individual header values, or pass an empty string to disable a specific header.

func Subscribe

func Subscribe[T any](bus *EventBus, event Event[T], handler func(T))

Subscribe registers a typed handler for an event without service ownership.

func SubscribeWithOwner

func SubscribeWithOwner[T any](bus *EventBus, service string, event Event[T], handler func(T))

SubscribeWithOwner registers a typed handler for an event with service ownership tracking.

func TypePtr added in v0.4.0

func TypePtr[T any]() *T

TypePtr returns a typed nil pointer of type T for use as a type token with the reflection-based Register*Service and Resolve methods, which take the type as a value rather than a type parameter.

func WantsJSON added in v0.3.5

func WantsJSON(r *http.Request) bool

WantsJSON reports whether the client prefers a JSON error body. A request that explicitly asks for text/html without also accepting application/json gets plain text; everything else, including an absent or */* Accept header, gets JSON.

Quality values are ignored: the rule is media-type token presence only.

func WithLogger

func WithLogger(ctx context.Context, logger Logger) context.Context

WithLogger returns a copy of ctx with the given Logger attached.

func WithPrincipal

func WithPrincipal(ctx context.Context, principal Principal) context.Context

WithPrincipal stores a Principal in the given context.

func WithRequestScope

func WithRequestScope(ctx context.Context, scope *Scope) context.Context

WithRequestScope adds a Scope instance to the context using a custom key for managing scoped service lifetimes. Useful for testing and managing scoped service lifetimes within request contexts.

func WriteError added in v0.3.5

func WriteError(w http.ResponseWriter, r *http.Request, err error) error

WriteError renders err as the unified error response, negotiating JSON or plain text via the Accept header. Values that are not, and do not wrap, an *Error render as a canonical 500 with the original discarded from the response.

WriteError does not log; the caller decides that. It reads only w and r, so it is safe to call from middleware that runs before the request scope exists.

Types

type App

type App struct {
	*Worker
	// contains filtered or unexported fields
}

App manages service lifecycle, the HTTP server, and graceful shutdown. It embeds a Worker for DI, events, migrations, and service management, and adds routing, CSRF protection, and an HTTP listener on top.

func NewApp

func NewApp(opts ...Option) *App

NewApp creates an App with the given options. Router and EventBus are created internally and registered in the container.

func (*App) Config

func (a *App) Config() *Config

Config retrieves the application's configuration.

func (*App) Handler

func (a *App) Handler() http.Handler

Handler returns the fully wrapped http.Handler used by the App's server (router behind CSRF protection). Useful for embedding the App in tests or in an externally managed listener.

func (*App) Router

func (a *App) Router() *Router

Router returns the App's router.

func (*App) Run

func (a *App) Run() error

Run initializes all services, runs pending migrations, starts the HTTP server, and blocks until a shutdown signal is received.

func (*App) Serve

func (a *App) Serve() error

Serve starts the HTTP server and blocks until it stops. A clean shutdown (http.ErrServerClosed) returns nil; any other listener error is returned.

func (*App) Server

func (a *App) Server() *http.Server

Server returns the App's *http.Server, constructing it on first call from the current Config. The instance is cached and safe to call concurrently.

func (*App) Start

func (a *App) Start() error

Start initializes the underlying Worker (services, migrations, hooks) and binds configuration. It does not start the HTTP server; call Serve for that, or use Run to do both and block until shutdown.

func (*App) Stop

func (a *App) Stop() error

Stop emits the server-shutting-down event, gracefully shuts down the HTTP server within ShutdownTimeout, and then shuts down the underlying Worker.

type AppConfigUpdatedPayload

type AppConfigUpdatedPayload struct {
	Config Config
}

AppConfigUpdatedPayload carries the updated config.

type AppOption

type AppOption func(*App)

AppOption configures HTTP-specific aspects of an App (error handler, CSRF, trusted origins). Only NewApp accepts AppOption values; passing one to NewWorker panics.

func WithCSRFDenyHandler

func WithCSRFDenyHandler(h http.Handler) AppOption

WithCSRFDenyHandler sets the handler invoked when a cross-origin request is rejected by CSRF protection. The default handler returns 403 Forbidden.

func WithCSRFInsecureBypassPattern

func WithCSRFInsecureBypassPattern(pattern string) AppOption

WithCSRFInsecureBypassPattern adds a URL path pattern that bypasses CSRF cross-origin protection. Use only for endpoints that require unauthenticated cross-origin access and implement their own request validation (e.g. webhook receivers).

func WithErrorHandler

func WithErrorHandler(h ErrorHandler) AppOption

WithErrorHandler configures the function that converts DI-aware handler errors into HTTP responses.

func WithTrustedOrigin

func WithTrustedOrigin(origin string) AppOption

WithTrustedOrigin adds an origin that is permitted to make cross-origin non-safe requests (POST, PUT, PATCH, DELETE, etc.). The origin must be an absolute URL with a scheme and host, e.g. "https://app.example.com". Panics if the origin is not a valid absolute URL.

type Authenticator

type Authenticator interface {
	Authenticate(ctx context.Context, r *http.Request) (Principal, error)
}

Authenticator extracts a Principal from an HTTP request. Implementations may use JWTs, server-side sessions, API keys, or any other credential scheme.

type Authorizer

type Authorizer interface {
	Authorize(ctx context.Context, principal Principal, action, resource string) error
}

Authorizer decides whether a Principal is allowed to perform an action on a resource. It is a separate concern from authentication — an Authenticator identifies who the caller is, while an Authorizer enforces what they can do.

type BasePrincipalMetadata

type BasePrincipalMetadata map[string]any

BasePrincipalMetadata is a map-based implementation of PrincipalMetadata.

func (BasePrincipalMetadata) Value

func (m BasePrincipalMetadata) Value(key string) any

Value returns the metadata value for the given key, or nil if not present.

type CacheControlOption

type CacheControlOption func(*CacheControlOptions)

CacheControlOption is a functional option for configuring CacheControl.

func WithCacheControlImmutable

func WithCacheControlImmutable() CacheControlOption

WithCacheControlImmutable appends the "immutable" directive.

func WithCacheControlMaxAge

func WithCacheControlMaxAge(val time.Duration) CacheControlOption

WithCacheControlMaxAge appends a "max-age" directive with the given duration.

func WithCacheControlMustRevalidate

func WithCacheControlMustRevalidate() CacheControlOption

WithCacheControlMustRevalidate appends the "must-revalidate" directive.

func WithCacheControlMustUnderstand

func WithCacheControlMustUnderstand() CacheControlOption

WithCacheControlMustUnderstand appends the "must-understand" directive.

func WithCacheControlNoCache

func WithCacheControlNoCache() CacheControlOption

WithCacheControlNoCache appends the "no-cache" directive.

func WithCacheControlNoStore

func WithCacheControlNoStore() CacheControlOption

WithCacheControlNoStore appends the "no-store" directive.

func WithCacheControlNoTransform

func WithCacheControlNoTransform() CacheControlOption

WithCacheControlNoTransform appends the "no-transform" directive.

func WithCacheControlPath

func WithCacheControlPath(val string) CacheControlOption

WithCacheControlPath adds an exact path that should receive the Cache-Control header.

func WithCacheControlPathPrefix

func WithCacheControlPathPrefix(val string) CacheControlOption

WithCacheControlPathPrefix adds a path prefix to match against. Any request whose path starts with this prefix will receive the Cache-Control header.

func WithCacheControlPathPrefixes

func WithCacheControlPathPrefixes(val []string) CacheControlOption

WithCacheControlPathPrefixes adds multiple path prefixes to match against.

func WithCacheControlPathSuffix

func WithCacheControlPathSuffix(val string) CacheControlOption

WithCacheControlPathSuffix adds a path suffix to match against (e.g., ".css", ".js"). Any request whose path ends with this suffix will receive the Cache-Control header.

func WithCacheControlPathSuffixes

func WithCacheControlPathSuffixes(val []string) CacheControlOption

WithCacheControlPathSuffixes adds multiple path suffixes to match against.

func WithCacheControlPaths

func WithCacheControlPaths(val []string) CacheControlOption

WithCacheControlPaths adds multiple exact paths that should receive the Cache-Control header.

func WithCacheControlPrivate

func WithCacheControlPrivate() CacheControlOption

WithCacheControlPrivate appends the "private" directive.

func WithCacheControlProxyRevalidate

func WithCacheControlProxyRevalidate() CacheControlOption

WithCacheControlProxyRevalidate appends the "proxy-revalidate" directive.

func WithCacheControlPublic

func WithCacheControlPublic() CacheControlOption

WithCacheControlPublic appends the "public" directive.

func WithCacheControlSMaxAge

func WithCacheControlSMaxAge(val time.Duration) CacheControlOption

WithCacheControlSMaxAge appends an "s-maxage" directive (shared/CDN cache max age).

func WithCacheControlStaleIfError

func WithCacheControlStaleIfError(val time.Duration) CacheControlOption

WithCacheControlStaleIfError appends a "stale-if-error" directive.

func WithCacheControlStaleWhileRevalidate

func WithCacheControlStaleWhileRevalidate(val time.Duration) CacheControlOption

WithCacheControlStaleWhileRevalidate appends a "stale-while-revalidate" directive.

type CacheControlOptions

type CacheControlOptions struct {
	// contains filtered or unexported fields
}

CacheControlOptions configures the behavior of the CacheControl middleware.

type CacheProvider

type CacheProvider interface {
	Get(ctx context.Context, key string) ([]byte, error)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	Delete(ctx context.Context, key string) error
	Exists(ctx context.Context, key string) (bool, error)
}

CacheProvider abstracts key-value caching. Implemented by in-memory, Redis, Valkey, or any other cache service.

type Config

type Config struct {
	// Embedded GasEnv
	env.WithGasEnv

	Server ServerSettings
}

Config holds server-level configuration passed from the host server to the App. Sensible defaults are applied via DefaultConfig().

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a Config with sensible defaults.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that the Config fields are valid.

type ConfigProvider

type ConfigProvider interface {
	SetDefault(key string, value any)
	SetDefaults(values any) error
	Set(key string, value any)
	Bind(dest any, options ...config.BindOption) error
	Get(key string) any
	Find(key string) (value any, exist bool)
	Values() map[string]any
}

ConfigProvider defines the config struct API, used by other modules that needs access to the config provider.

type Context

type Context interface {
	context.Context
	// ResponseWriter returns the underlying http.ResponseWriter.
	ResponseWriter() http.ResponseWriter
	// Request returns the underlying *http.Request.
	Request() *http.Request
	// JSON serializes v as JSON and writes it with the given status code.
	JSON(status int, v any) error
	// XML serializes v as XML and writes it with the given status code.
	// Uses content type "application/xml; charset=utf-8".
	XML(status int, v any) error
	// RSS serializes v as XML with content type "application/rss+xml; charset=utf-8".
	RSS(status int, v any) error
	// HTML writes an HTML response with the given status code and content string.
	HTML(status int, s string) error
	// Text writes a plain-text response with the given status code.
	Text(status int, s string) error
	// NoContent writes a 204 No Content response.
	NoContent() error

	// WriteError writes err as the unified error response, negotiating JSON or
	// plain text the same way the default ErrorHandler does. Values that are
	// not, and do not wrap, an *Error render as a canonical 500.
	WriteError(err error) error
	// WriteErrorJSON writes the JSON error envelope regardless of the Accept
	// header. Use it for JSON API routes in an app whose ErrorHandler renders
	// HTML.
	WriteErrorJSON(err error) error

	// BadRequest returns a 400 *Error with code CodeBadRequest.
	BadRequest(msg string) error
	// Unauthorized returns a 401 *Error with code CodeUnauthorized.
	Unauthorized(msg string) error
	// Forbidden returns a 403 *Error with code CodeForbidden.
	Forbidden(msg string) error
	// NotFound returns a 404 *Error with code CodeNotFound.
	NotFound(msg string) error
	// Conflict returns a 409 *Error with code CodeConflict.
	Conflict(msg string) error
	// Unprocessable returns a 422 *Error with code CodeValidationFailed.
	Unprocessable(msg string) error
	// TooManyRequests returns a 429 *Error with code CodeRateLimited.
	TooManyRequests(msg string) error
	// Internal returns a 500 *Error with code CodeInternal.
	Internal(msg string) error
	// ServiceUnavailable returns a 503 *Error with code CodeUnavailable.
	ServiceUnavailable(msg string) error

	// BadRequestErr returns a 400 *Error with code CodeBadRequest, caused by err.
	BadRequestErr(err error, msg string) error
	// UnauthorizedErr returns a 401 *Error with code CodeUnauthorized, caused by err.
	UnauthorizedErr(err error, msg string) error
	// ForbiddenErr returns a 403 *Error with code CodeForbidden, caused by err.
	ForbiddenErr(err error, msg string) error
	// NotFoundErr returns a 404 *Error with code CodeNotFound, caused by err.
	NotFoundErr(err error, msg string) error
	// ConflictErr returns a 409 *Error with code CodeConflict, caused by err.
	ConflictErr(err error, msg string) error
	// UnprocessableErr returns a 422 *Error with code CodeValidationFailed,
	// caused by err.
	UnprocessableErr(err error, msg string) error
	// TooManyRequestsErr returns a 429 *Error with code CodeRateLimited,
	// caused by err.
	TooManyRequestsErr(err error, msg string) error
	// InternalErr returns a 500 *Error with code CodeInternal, caused by err.
	InternalErr(err error, msg string) error
	// ServiceUnavailableErr returns a 503 *Error with code CodeUnavailable,
	// caused by err.
	ServiceUnavailableErr(err error, msg string) error

	// Redirect sends an HTTP redirect to the given URL with the given status code.
	Redirect(status int, url string)
	// Param returns the URL parameter value by name (chi.URLParam).
	Param(key string) string
	// Query returns the query string parameter value by name.
	Query(key string) string
	// Header returns the request header value by name.
	Header(key string) string
	// SetHeader sets a response header.
	SetHeader(key, value string)

	// BindJSON decodes the request body as JSON into dest and performs automatic validation
	// using the configured validator.
	BindJSON(dest any) error
	// BindForm binds form data from the HTTP request to the provided destination object
	// and performs automatic validation using the configured validator.
	BindForm(dest any) error
	// Validator returns the *validator.Validate used for request validation,
	// building the package default on first use if none was supplied.
	Validator() *validator.Validate
	// FormDecoder returns the *schema.Decoder used to decode form data into
	// structs, building the package default on first use if none was supplied.
	FormDecoder() *schema.Decoder

	// Principal returns the authenticated Principal attached to the request,
	// or a 401 *Error with code CodeUnauthorized when none is present.
	Principal() (Principal, error)
}

Context is the first parameter of every DI-aware handler. It wraps the HTTP response writer and request into a single value. The per-request scope is accessible via RequestScope(c.Request()) — the adapter resolves dependencies automatically, so handlers rarely need to access the scope directly.

func NewContext

func NewContext(parent context.Context, w http.ResponseWriter, r *http.Request, opts ...ContextOption) Context

NewContext creates a Context from the standard HTTP pair. It panics if parent, w, or r is nil.

The returned Context is installed as the request's own context, so Request().Context() and the Context itself are the same value.

type ContextOption

type ContextOption func(*reqContext)

ContextOption is a functional option used to modify or extend the behavior of a reqContext at creation time.

func WithFormDecoder

func WithFormDecoder(d *schema.Decoder) ContextOption

WithFormDecoder sets a custom form decoder for the reqContext using the provided *schema.Decoder instance.

func WithValidate

func WithValidate(v *validator.Validate) ContextOption

WithValidate returns a ContextOption that sets the provided *validator.Validate instance to the reqContext.

type DatabaseProvider

type DatabaseProvider interface {
	DB() *sql.DB
	Driver() string
	Ping(ctx context.Context) error
	Query(ctx context.Context, query string, args ...any) (Rows, error)
	Exec(ctx context.Context, query string, args ...any) (Result, error)
	BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
	WithTx(ctx context.Context, opts *sql.TxOptions, fn func(*sql.Tx) error) (err error)
}

DatabaseProvider abstracts database access. Implemented by gas/database or any other database service. DB() exposes the underlying *sql.DB so that sqlc-generated code and transactions can use it directly.

type Email

type Email struct {
	From     string
	ReplyTo  string
	Subject  string
	TextBody string
	HTMLBody string

	Headers map[string]string

	To  []string
	Cc  []string
	Bcc []string
}

Email represents an email message.

type EmailProvider

type EmailProvider interface {
	Send(ctx context.Context, msg *Email) error
	SendFromTemplate(ctx context.Context, msg *TemplatedEmail) error
}

EmailProvider abstracts email sending.

type EnqueueOption

type EnqueueOption func(*enqueueOptions)

EnqueueOption configures an Enqueue call.

func WithDedupeID

func WithDedupeID(id string) EnqueueOption

WithDedupeID sets a deduplication identifier.

func WithDelay

func WithDelay(d time.Duration) EnqueueOption

WithDelay sets an initial delay before the job becomes visible to consumers.

func WithGroupID

func WithGroupID(id string) EnqueueOption

WithGroupID sets the message group for FIFO queue ordering.

func WithJobAttributes

func WithJobAttributes(attrs map[string]string) EnqueueOption

WithJobAttributes attaches provider-specific key-value metadata to the job.

type Error added in v0.3.5

type Error struct {
	Status  int            `json:"-"`
	Code    string         `json:"code"`
	Message string         `json:"message"`
	Fields  []FieldError   `json:"fields,omitempty"`
	Details map[string]any `json:"details,omitempty"`
	// contains filtered or unexported fields
}

Error is the unified error shape for Gas handlers. Return it from a DI-aware handler and the ErrorHandler renders it; see WriteError for the wire format.

Status is carried by the HTTP status line and is deliberately absent from the JSON body, so the two can never disagree.

The wrapped cause is unexported and never serialized. It reaches logs and errors.Is / errors.As, never a client.

Field order is the JSON key order of the response body, so it is fixed by the wire contract rather than by memory layout.

func AsError added in v0.3.5

func AsError(err error) (*Error, bool)

AsError reports whether err is, or wraps, an *Error and returns it.

func BadRequest added in v0.3.5

func BadRequest(message string) *Error

BadRequest returns a 400 Error with code CodeBadRequest.

func Conflict added in v0.3.5

func Conflict(message string) *Error

Conflict returns a 409 Error with code CodeConflict.

func Forbidden added in v0.3.5

func Forbidden(message string) *Error

Forbidden returns a 403 Error with code CodeForbidden.

func Internal added in v0.3.5

func Internal(message string) *Error

Internal returns a 500 Error with code CodeInternal.

func NewError added in v0.3.5

func NewError(status int, code, message string) *Error

NewError builds an Error with an explicit status, code, and message.

func NotFound added in v0.3.5

func NotFound(message string) *Error

NotFound returns a 404 Error with code CodeNotFound.

func ServiceUnavailable added in v0.3.5

func ServiceUnavailable(message string) *Error

ServiceUnavailable returns a 503 Error with code CodeUnavailable.

func TooManyRequests added in v0.3.5

func TooManyRequests(message string) *Error

TooManyRequests returns a 429 Error with code CodeRateLimited.

func Unauthorized added in v0.3.5

func Unauthorized(message string) *Error

Unauthorized returns a 401 Error with code CodeUnauthorized.

func Unprocessable added in v0.3.5

func Unprocessable(message string) *Error

Unprocessable returns a 422 Error with code CodeValidationFailed.

func (*Error) Error added in v0.3.5

func (e *Error) Error() string

Error implements the error interface. The wrapped cause is included so that logs carry the full chain.

func (*Error) Unwrap added in v0.3.5

func (e *Error) Unwrap() error

Unwrap returns the wrapped cause, so errors.Is and errors.As see through it.

func (*Error) WithCause added in v0.3.5

func (e *Error) WithCause(err error) *Error

WithCause attaches an underlying cause. The cause is logged and is reachable through errors.Is and errors.As, but is never written to a response.

func (*Error) WithDetail added in v0.3.5

func (e *Error) WithDetail(key string, val any) *Error

WithDetail attaches an arbitrary key/value pair to the response payload.

func (*Error) WithField added in v0.3.5

func (e *Error) WithField(field, rule, message string) *Error

WithField appends a field-level validation failure.

type ErrorHandler

type ErrorHandler func(ctx Context, err error)

ErrorHandler converts a handler error into an HTTP response.

type ErrorResponse added in v0.3.5

type ErrorResponse struct {
	Error *Error `json:"error"`
}

ErrorResponse is the wire shape of every error response Gas writes. It is exported so Go clients and tests can decode it.

type Event

type Event[TData any] struct {
	Name string
}

Event is a typed event definition

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

EventBus is a publish/subscribe message bus with service ownership tracking.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates an empty EventBus.

func (*EventBus) Emit

func (bus *EventBus) Emit(event string, data any) *sync.WaitGroup

Emit dispatches an event to all subscribers concurrently and returns a WaitGroup.

func (*EventBus) RemoveByService added in v0.3.5

func (bus *EventBus) RemoveByService(service string)

RemoveByService removes all subscriptions registered by the given service.

func (*EventBus) Subscribe

func (bus *EventBus) Subscribe(event string, handler func(any))

Subscribe registers a handler for an event without service ownership. Use SubscribeWithOwner when subscribing from a service so that RemoveByService can clean up subscriptions.

func (*EventBus) SubscribeWithOwner

func (bus *EventBus) SubscribeWithOwner(service, event string, handler func(any))

SubscribeWithOwner registers a handler for an event with service ownership tracking. The base server uses this ownership info during kill-switch to remove all subscriptions belonging to a closed service.

type FieldError added in v0.3.5

type FieldError struct {
	Field   string `json:"field"`
	Rule    string `json:"rule"`
	Message string `json:"message"`
}

FieldError describes a single field-level validation failure. Field carries the name the client sent (the json tag), not the Go struct field name.

type HealthProvider

type HealthProvider interface {
	CheckHealth(ctx context.Context) map[string]error
}

HealthProvider aggregates health signals across all active services that implement HealthReporter. The returned map keys are service names; a nil value indicates a healthy service.

type HealthReporter

type HealthReporter interface {
	CheckHealth(ctx context.Context) error
}

HealthReporter is implemented by services that can report their own health. Answers the question "am I alive?" — a failure here signals that the process is in a broken state that a restart would resolve (lost connection that won't recover, corrupt internal state, deadlock). Maps to a Kubernetes livenessProbe. A nil return means healthy; a non-nil error describes the failure.

type Job

type Job struct {
	ID            string
	ReceiptHandle string            // opaque token used by Ack/Nack
	Attributes    map[string]string // provider-specific metadata
	Body          []byte
}

Job represents a message received from a queue.

type JobQueueProvider

type JobQueueProvider interface {
	Enqueue(ctx context.Context, queue string, payload []byte, opts ...EnqueueOption) error
	Dequeue(ctx context.Context, queue string, maxMessages int, wait time.Duration) ([]Job, error)
	Ack(ctx context.Context, queue string, job Job) error
	Nack(ctx context.Context, queue string, job Job) error
}

JobQueueProvider abstracts async job/message queue processing. Implemented by gas/queue/sqs or any other queue service. The interface is pull-based: consumers call Dequeue in their own worker loop and acknowledge results with Ack/Nack.

type LogEvent

type LogEvent interface {
	Str(key, val string) LogEvent
	Int(key string, val int) LogEvent
	Int64(key string, val int64) LogEvent
	Float64(key string, val float64) LogEvent
	Bool(key string, val bool) LogEvent
	Err(key string, val error) LogEvent
	Duration(key string, val time.Duration) LogEvent
	Any(key string, val any) LogEvent
	// Send finalizes and emits the log event.
	Send()
}

LogEvent is a single structured log entry. Field methods return the same LogEvent for chaining. Call Send to finalize and emit the event.

type Logger

type Logger interface {
	// Trace starts a log event at the TRACE level.
	Trace(msg string) LogEvent
	// Debug starts a log event at the DEBUG level.
	Debug(msg string) LogEvent
	// Info starts a log event at the INFO level.
	Info(msg string) LogEvent
	// Warn starts a log event at the WARN level.
	Warn(msg string) LogEvent
	// Error starts a log event at the ERROR level.
	Error(msg string) LogEvent

	// With returns a LoggerContext for building a sub-logger with persistent fields.
	With() LoggerContext

	// SetBaseFields returns a MutableLoggerContext for attaching persistent fields
	// directly to this logger instance. Unlike With, which branches into a new logger,
	// SetBaseFields mutates the receiver so that all subsequent log events carry the
	// accumulated fields. Intended for use by request-scoped middleware.
	SetBaseFields() MutableLoggerContext

	// Flush writes any buffered log entries to the underlying output.
	Flush()
}

Logger is the provider interface for structured logging in the Gas ecosystem. Implementations produce LogEvent values at a given severity level. Each method returns a LogEvent that can be enriched with typed fields before calling Send.

Use With to derive a sub-logger that carries persistent fields across all subsequent log events.

Call Flush to ensure all buffered log entries are written (e.g. before shutdown).

func LoggerFromContext

func LoggerFromContext(ctx context.Context) Logger

LoggerFromContext returns the Logger stored in ctx, or nil if none is present.

type LoggerContext

type LoggerContext interface {
	Str(key, val string) LoggerContext
	Int(key string, val int) LoggerContext
	Int64(key string, val int64) LoggerContext
	Float64(key string, val float64) LoggerContext
	Bool(key string, val bool) LoggerContext
	Err(key string, val error) LoggerContext
	Duration(key string, val time.Duration) LoggerContext
	Any(key string, val any) LoggerContext
	// Logger returns a new Logger that includes all fields added to this context.
	Logger() Logger
}

LoggerContext is a builder for deriving a sub-logger with persistent fields. Each field method returns the same LoggerContext for chaining. Call Logger to obtain the resulting Logger that carries all accumulated fields.

type Middleware

type Middleware struct {
	// contains filtered or unexported fields
}

Middleware represents either a named middleware (resolved from the router's registry at apply time) or an inline middleware function.

func MiddlewareByName

func MiddlewareByName(name string) Middleware

MiddlewareByName creates a Middleware that will be resolved by name from the router's internal registry when applied.

func MiddlewareFunc

func MiddlewareFunc(fn func(http.Handler) http.Handler) Middleware

MiddlewareFunc creates a Middleware that wraps an inline handler function directly. The middleware will be anonymous and omitted from the route map. Use MiddlewareFuncWithName to give it a display name.

func MiddlewareFuncWithName

func MiddlewareFuncWithName(name string, fn func(http.Handler) http.Handler) Middleware

MiddlewareFuncWithName creates a named inline Middleware. The name appears in the route map alongside routes that this middleware applies to.

type Migration

type Migration struct {
	Version     string
	Service     string
	Description string
	Up          string
	Down        string
}

Migration represents a single database migration owned by a service.

type MigrationManager

type MigrationManager interface {
	Service

	// Register adds a migration and tracks which service owns it.
	Register(service string, m Migration)

	// RegisterSlice adds multiple migrations at once for the given service.
	RegisterSlice(service string, migrations []Migration)

	// RegisterFS reads .up.sql/.down.sql files from an fs.FS and registers
	// them as migrations for the given service. Files must follow the naming
	// convention: {version}_{description}.up.sql / {version}_{description}.down.sql
	// (e.g. 20250216_001_create_users.up.sql).
	RegisterFS(service string, fsys fs.FS) error

	// RunPending applies all unapplied migrations in global version order.
	// If any migration is marked dirty, execution is blocked until the
	// dirty state is manually resolved.
	RunPending() error

	// Down reverses the last n applied migrations in reverse version order.
	Down(n int) error
}

MigrationManager is the interface for registering and executing database migrations. Services call Register during Init() to declare their migrations. The implementation lives in a separate service (gas/migrate) and is wired in by the App.

type MutableLoggerContext

type MutableLoggerContext interface {
	Str(key, val string) MutableLoggerContext
	Int(key string, val int) MutableLoggerContext
	Int64(key string, val int64) MutableLoggerContext
	Float64(key string, val float64) MutableLoggerContext
	Bool(key string, val bool) MutableLoggerContext
	Err(key string, val error) MutableLoggerContext
	Duration(key string, val time.Duration) MutableLoggerContext
	Any(key string, val any) MutableLoggerContext
	// Apply mutates the originating Logger in place with all accumulated fields.
	Apply()
}

MutableLoggerContext is a builder for attaching persistent fields directly to an existing Logger instance. Unlike LoggerContext (returned by With), which produces a new branched Logger, MutableLoggerContext.Apply() mutates the receiver in place. Intended for use in request-scoped middleware where the same Logger instance is shared across the whole request.

type NopLogEvent

type NopLogEvent struct{}

NopLogEvent is a LogEvent that discards all fields and performs no action on Send. All methods return the receiver for chaining.

func (*NopLogEvent) Any

func (e *NopLogEvent) Any(string, any) LogEvent

Any adds a key-value pair to the event, where the value can be of any type, returning the event for chaining.

func (*NopLogEvent) Bool

func (e *NopLogEvent) Bool(string, bool) LogEvent

Bool adds a boolean field to the log event and returns the event for method chaining.

func (*NopLogEvent) Duration

func (e *NopLogEvent) Duration(string, time.Duration) LogEvent

Duration adds a field with a duration value to the log entry, identified by the given key.

func (*NopLogEvent) Err

func (e *NopLogEvent) Err(string, error) LogEvent

Err adds an error field to the log event with the specified key and value. Returns the log event for chaining.

func (*NopLogEvent) Float64

func (e *NopLogEvent) Float64(string, float64) LogEvent

Float64 sets a float64 value for the specified key in the log event. It returns the LogEvent instance for chaining.

func (*NopLogEvent) Int

func (e *NopLogEvent) Int(string, int) LogEvent

Int adds an integer field to the log event with the provided key and value, returning the LogEvent for chaining.

func (*NopLogEvent) Int64

func (e *NopLogEvent) Int64(string, int64) LogEvent

Int64 adds an int64 field to the log event with the specified key and value. It returns the LogEvent for chaining.

func (*NopLogEvent) Send

func (e *NopLogEvent) Send()

Send finalizes the log event. In NopLogEvent, it performs no action and discards the event.

func (*NopLogEvent) Str

func (e *NopLogEvent) Str(string, string) LogEvent

Str adds a string field with the given key and value to the log event and returns the event for chaining.

type NopLogger

type NopLogger struct{}

NopLogger is a Logger implementation that silently discards all log output. It uses a singleton pattern — all instances share the same underlying value, resulting in zero allocations per log call.

func (*NopLogger) Debug

func (l *NopLogger) Debug(string) LogEvent

Debug logs a debug-level message and returns a no-op LogEvent for chaining.

func (*NopLogger) Error

func (l *NopLogger) Error(string) LogEvent

Error logs an error-level message with no operational effect, returning a no-op LogEvent.

func (*NopLogger) Flush

func (l *NopLogger) Flush()

Flush performs no operation as NopLogger discards all log output without buffering.

func (*NopLogger) Info

func (l *NopLogger) Info(string) LogEvent

Info logs an informational message and returns a no-op LogEvent instance.

func (*NopLogger) SetBaseFields

func (l *NopLogger) SetBaseFields() MutableLoggerContext

SetBaseFields returns a no-op MutableLoggerContext associated with the NopLogger instance.

func (*NopLogger) Trace

func (l *NopLogger) Trace(string) LogEvent

Trace creates a no-op log event for a trace-level message and silently discards it.

func (*NopLogger) Warn

func (l *NopLogger) Warn(string) LogEvent

Warn logs a warning-level message and returns a no-op LogEvent instance.

func (*NopLogger) With

func (l *NopLogger) With() LoggerContext

With creates a new LoggerContext for deriving a sub-logger with persistent fields across log events.

type NopLoggerContext

type NopLoggerContext struct{}

NopLoggerContext is a LoggerContext that discards all fields. All methods return the receiver for chaining and Logger returns a NopLogger.

func (*NopLoggerContext) Any

Any adds a field with the given key and value to the context, supporting any type, and returns the LoggerContext.

func (*NopLoggerContext) Bool

Bool adds a boolean field to the logging context under the specified key for structured logging.

func (*NopLoggerContext) Duration

Duration adds a time.Duration field with the specified key and value to the logging context and returns it.

func (*NopLoggerContext) Err

Err adds an error field to the logger context with the specified key and error value, returning the same context.

func (*NopLoggerContext) Float64

Float64 adds a float64 field with the specified key and value to the context and returns the LoggerContext for chaining.

func (*NopLoggerContext) Int

Int adds an integer field to the logger context with the specified key. Returns the same LoggerContext for chaining.

func (*NopLoggerContext) Int64

Int64 adds a key-value pair with an int64 value to the log context and returns the updated LoggerContext.

func (*NopLoggerContext) Logger

func (c *NopLoggerContext) Logger() Logger

Logger returns a no-op Logger instance that discards all log events.

func (*NopLoggerContext) Str

Str adds a string key-value pair to the logger context and returns the same context for chaining.

type NopLoggerCtor

type NopLoggerCtor func() *NopLogger

NopLoggerCtor defines a constructor function that returns a nop-logger implementing the Logger interface.

func NewNopLogger

func NewNopLogger() NopLoggerCtor

NewNopLogger returns a NopLoggerCtor that constructs a nop-logger implementing the Logger interface.

type NopMutableLoggerContext

type NopMutableLoggerContext struct{}

NopMutableLoggerContext is a MutableLoggerContext that discards all fields. All methods return the receiver for chaining and Apply is a no-op.

func (*NopMutableLoggerContext) Any

Any adds a custom key-value pair with a generic value to the context and returns the current MutableLoggerContext.

func (*NopMutableLoggerContext) Apply

func (c *NopMutableLoggerContext) Apply()

Apply performs a no-op operation and returns without modifying any state or producing side effects.

func (*NopMutableLoggerContext) Bool

Bool adds a boolean field with the given key and value to the logger context and returns the context for chaining.

func (*NopMutableLoggerContext) Duration

Duration adds a duration field to the logging context with the specified key and value.

func (*NopMutableLoggerContext) Err

Err adds an error field to the logger context and returns the same context for chaining.

func (*NopMutableLoggerContext) Float64

Float64 adds a float64 key-value pair to the context without storing it and returns the receiver for chaining.

func (*NopMutableLoggerContext) Int

Int adds an integer field to the context and returns the updated MutableLoggerContext.

func (*NopMutableLoggerContext) Int64

Int64 adds a key-value pair where the value is an int64 and returns the current MutableLoggerContext for chaining.

func (*NopMutableLoggerContext) Str

Str adds a string field with the given key and value to the logger context and returns the context for chaining.

type ObjectInfo

type ObjectInfo struct {
	LastModified time.Time
	Metadata     map[string]string
	ContentType  string
	Size         int64
}

ObjectInfo holds metadata about an object without its body, returned by Head.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option is a marker interface satisfied by both WorkerOption and AppOption. NewWorker and NewApp accept ...Option so callers can pass either type.

type Principal

type Principal interface {
	Subject() string
	Scheme() string
	CredentialID() string
	Metadata() PrincipalMetadata
}

Principal represents an authenticated identity. Subject is the stable user identifier, Scheme identifies the authentication method (e.g. "jwt", "session", "apikey"), and CredentialID is the specific credential instance (session ID, JWT jti, API key ID).

func MustPrincipalFromContext

func MustPrincipalFromContext(ctx context.Context) Principal

MustPrincipalFromContext retrieves the Principal from the context and panics if no principal is present.

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) Principal

PrincipalFromContext retrieves the Principal from the context, or nil if no principal is present.

type PrincipalMetadata

type PrincipalMetadata interface {
	Value(key string) any
}

PrincipalMetadata provides read-only access to arbitrary key-value metadata attached to a Principal at construction time.

type PrincipalRevoker

type PrincipalRevoker interface {
	Revoke(ctx context.Context, principal Principal) error
	RevokeAll(ctx context.Context, subject string) error
	RevokeAllByScheme(ctx context.Context, subject, scheme string) error
}

PrincipalRevoker invalidates credentials associated with a Principal. Use Revoke to invalidate a single credential, RevokeAll to invalidate every credential for a subject, or RevokeAllByScheme to invalidate all credentials of a specific scheme (e.g. all sessions, all API keys) for a subject.

type ReadyProvider

type ReadyProvider interface {
	CheckReady(ctx context.Context) map[string]error
}

ReadyProvider aggregates readiness signals across all active services that implement ReadyReporter. The returned map keys are service names; a nil value indicates a ready service.

type ReadyReporter

type ReadyReporter interface {
	CheckReady(ctx context.Context) error
}

ReadyReporter is implemented by services that can report whether they are ready to accept traffic. Answers the question "should traffic be routed to me?" — distinct from health, since a service can be alive but not yet ready (warming caches, pending migrations, dependency briefly unavailable, draining during shutdown). Maps to a Kubernetes readinessProbe. A nil return means ready; a non-nil error describes why the service is not ready.

type RegisteredRoute

type RegisteredRoute struct {
	Method     string
	Path       string
	Middleware []string
}

RegisteredRoute is an exported snapshot of a registered route.

type RequestLoggerOption

type RequestLoggerOption func(*RequestLoggerOptions)

RequestLoggerOption is a functional option for configuring RequestLogger.

func WithRequestLoggerAppendRequestID

func WithRequestLoggerAppendRequestID(val bool) RequestLoggerOption

WithRequestLoggerAppendRequestID controls whether the request ID (from chi's RequestID middleware) is added to the logger's base fields. Defaults to true.

func WithRequestLoggerBuilder

func WithRequestLoggerBuilder(fn func(Logger, *http.Request) Logger) RequestLoggerOption

WithRequestLoggerBuilder sets a function that customizes the logger used for each request. The builder receives the resolved Logger and the current request, and returns a (possibly wrapped or enriched) Logger. It is called after the request ID field is appended (if enabled). This can be used to add extra fields, swap the logger, or adjust log levels per request.

type RequestLoggerOptions

type RequestLoggerOptions struct {
	// contains filtered or unexported fields
}

RequestLoggerOptions configures the behavior of the RequestLogger middleware.

type Resolver

type Resolver interface {
	// contains filtered or unexported methods
}

Resolver is implemented by ServiceContainer and Scope to provide dependency resolution. The unexported method restricts implementation to this package.

type Result

type Result interface {
	RowsAffected() (int64, error)
	LastInsertId() (int64, error)
}

Result represents the outcome of an Exec operation.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router is a smart router that wraps Chi and tracks route and middleware ownership by service. MiddlewareByName middleware is validated against an internal registry at registration time and resolved on every build of the routing tree, so ownership changes reach chains registered earlier. The base server uses RemoveByService during kill-switch to replace everything a closed service registered — its routes and its named middleware — with 503.

Top-level routers created via NewRouter() start unsealed: Use, Handle, Group, and Route calls are deferred until Seal() is called. This lets services register middleware and routes in any order during Init(). Seal() flushes all pending middleware first (via chi.Use), then replays pending route operations — guaranteeing middleware-before-routes ordering that Chi requires.

Sub-routers (created inside Group/Route callbacks) are always sealed so their calls pass through to Chi immediately.

Concurrency model: once sealed, the live chi tree is immutable. ServeHTTP reads it lock-free via an atomic pointer (served). Runtime mutators (RemoveByService, and Handle/Route/Group/Use via RestartService) never touch the tree in flight — they rebuild a fresh chi tree under r.mu by replaying the recorded registrations and atomically swap it in. In-flight requests keep serving the previous tree, which is never written after publication.

func NewRouter

func NewRouter() *Router

NewRouter creates a Router backed by Chi with an empty middleware registry. The router starts unsealed — Use/Handle/Group/Route calls are deferred until Seal() is called.

func (*Router) Group

func (r *Router) Group(fn func(sub *Router))

Group creates an inline route group. The callback receives a sub-Router that shares the parent's middleware registry and route tracking. When the router is unsealed, the group is deferred until Seal().

func (*Router) Handle

func (r *Router) Handle(service, method, path string, handler any, middleware ...Middleware)

Handle registers a route and tracks ownership. The handler can be:

  • http.HandlerFunc or func(http.ResponseWriter, *http.Request) — passed through directly
  • A DI-aware function: func(gas.Context, Dep1, Dep2, ...) error — dependencies are auto-resolved from the per-request scope at call time

Middleware is resolved from the internal registry (for MiddlewareByName) or used directly (for MiddlewareFunc) and applied in order (outermost first). Panics if a named middleware is not registered or if a DI-aware handler has an invalid signature. When the router is unsealed, the registration is deferred until Seal().

func (*Router) Mux

func (r *Router) Mux() chi.Router

Mux returns the underlying Chi router so the base server can add global middleware, mount sub-routers, or pass it to http.Server.

This returns the most recently built tree. Mutating it directly bypasses the router's synchronization and is only safe before serving begins.

func (*Router) NamedMiddleware

func (r *Router) NamedMiddleware() map[string]string

NamedMiddleware returns a snapshot of the named middleware registry as a map of middleware name to owning service.

func (*Router) NotFound

func (r *Router) NotFound(service string, handler any)

NotFound registers a custom not-found handler for the router, associated with the specified service. The handler can be http.HandlerFunc or a DI-aware function (same rules as Handle). Panics if a not found handler is already registered by another service.

func (*Router) Register

func (r *Router) Register(service, name string, mw func(http.Handler) http.Handler)

Register adds a named middleware to the internal registry and tracks which service owns it.

func (*Router) RemoveByService added in v0.3.5

func (r *Router) RemoveByService(service string)

RemoveByService tears down everything the given service registered. Its routes are replaced with 503 Service Unavailable handlers, and every named middleware it registered is replaced with a 503 short-circuit wherever it is referenced — including on routes owned by services that are still running, and including references nested inside Group/Route blocks. A route guarded by a killed service's middleware therefore stops serving: the middleware is disabled, never skipped, so a teardown can never drop an authorization check and leave the route open.

Register-ing the name again (the RestartService -> Init path) re-arms it.

The teardown is applied by rebuilding a fresh routing tree and swapping it in atomically, so requests in flight on the old tree are never disrupted.

func (*Router) Route

func (r *Router) Route(pattern string, fn func(sub *Router))

Route creates a pattern-scoped route group. The callback receives a sub-Router that shares the parent's middleware registry and route tracking. When the router is unsealed, the route is deferred until Seal().

Calling Route with the same pattern more than once on the same parent is allowed: subsequent calls attach to the sub-mux created by the first call instead of panicking. Each call's body runs inside its own chi.Group, so middleware added via Use() inside a given block only applies to handlers registered in that block.

func (*Router) Routes

func (r *Router) Routes() map[string][]RegisteredRoute

Routes returns a snapshot of all explicitly registered routes grouped by service. Implicit HEAD routes (auto-registered alongside GET) are excluded.

func (*Router) Seal

func (r *Router) Seal()

Seal builds the routing tree from the deferred middleware and route registrations and publishes it. Middleware is applied first (via chi.Use), then route operations are replayed in order. After Seal, structural changes rebuild the tree and swap it in atomically. Seal is idempotent — calling it on an already-sealed router is a no-op.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements http.Handler, delegating to the published Chi tree. The tree is loaded atomically with no lock, so serving never contends with runtime route mutations.

func (*Router) SetErrorHandler

func (r *Router) SetErrorHandler(h ErrorHandler)

SetErrorHandler configures the function that converts DI-aware handler errors into HTTP responses. Must be called before Handle() registrations.

func (*Router) Use

func (r *Router) Use(middleware ...Middleware)

Use applies middleware to the router. Each Middleware is resolved (by name from the registry or used directly) and applied via chi's Use. Panics if a named middleware is not registered. When the router is unsealed, middleware is queued and applied during Seal().

func (*Router) UseMiddlewareByName

func (r *Router) UseMiddlewareByName(middleware string)

UseMiddlewareByName applies a middleware to the router by resolving it from the registry using its name. Panics if the middleware is not registered.

func (*Router) UseMiddlewareFunc

func (r *Router) UseMiddlewareFunc(middleware func(http.Handler) http.Handler)

UseMiddlewareFunc applies a middleware function directly to the router by wrapping it as a MiddlewareFunc.

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Close() error
	Err() error
}

Rows represents the result set of a query. Mirrors the standard database/sql Rows interface so implementations can wrap it directly.

type Scope

type Scope struct {
	// contains filtered or unexported fields
}

Scope is a resolution context for scoped service lifetimes.

func RequestScope

func RequestScope(r *http.Request) *Scope

RequestScope returns the per-request Scope from the request context. Panics if called outside the scope middleware (i.e. before InitServices installs it, or on a non-App-managed handler).

func (*Scope) Close

func (s *Scope) Close() error

Close calls Close() on all scoped Service instances resolved in this scope, in reverse resolution order, so a scoped service can still use the scoped dependencies it was built from.

type SecurityHeadersOption

type SecurityHeadersOption func(*SecurityHeadersOptions)

SecurityHeadersOption is a functional option for configuring SecurityHeaders.

func WithSecurityHeadersContentSecurityPolicy

func WithSecurityHeadersContentSecurityPolicy(val string) SecurityHeadersOption

WithSecurityHeadersContentSecurityPolicy sets the Content-Security-Policy header value. Pass an empty string to disable this header. No default is applied, since CSP values are highly application-specific.

func WithSecurityHeadersContentTypeOptions

func WithSecurityHeadersContentTypeOptions(val string) SecurityHeadersOption

WithSecurityHeadersContentTypeOptions sets the X-Content-Type-Options header value. Pass an empty string to disable this header. Default: "nosniff".

func WithSecurityHeadersCrossOriginOpenerPolicy

func WithSecurityHeadersCrossOriginOpenerPolicy(val string) SecurityHeadersOption

WithSecurityHeadersCrossOriginOpenerPolicy sets the Cross-Origin-Opener-Policy header value. Pass an empty string to disable this header. No default is applied.

func WithSecurityHeadersCrossOriginResourcePolicy

func WithSecurityHeadersCrossOriginResourcePolicy(val string) SecurityHeadersOption

WithSecurityHeadersCrossOriginResourcePolicy sets the Cross-Origin-Resource-Policy header value. Pass an empty string to disable this header. No default is applied.

func WithSecurityHeadersFrameOptions

func WithSecurityHeadersFrameOptions(val string) SecurityHeadersOption

WithSecurityHeadersFrameOptions sets the X-Frame-Options header value. Pass an empty string to disable this header. Default: "DENY".

func WithSecurityHeadersPermissionsPolicy

func WithSecurityHeadersPermissionsPolicy(val string) SecurityHeadersOption

WithSecurityHeadersPermissionsPolicy sets the Permissions-Policy header value. Pass an empty string to disable this header. Default: "camera=(), microphone=(), geolocation=()".

func WithSecurityHeadersReferrerPolicy

func WithSecurityHeadersReferrerPolicy(val string) SecurityHeadersOption

WithSecurityHeadersReferrerPolicy sets the Referrer-Policy header value. Pass an empty string to disable this header. Default: "strict-origin-when-cross-origin".

func WithSecurityHeadersStrictTransportSecurity

func WithSecurityHeadersStrictTransportSecurity(val string) SecurityHeadersOption

WithSecurityHeadersStrictTransportSecurity sets the Strict-Transport-Security (HSTS) header value. Pass an empty string to disable this header. No default is applied, since HSTS should only be enabled once the site is fully served over HTTPS.

type SecurityHeadersOptions

type SecurityHeadersOptions struct {
	// contains filtered or unexported fields
}

SecurityHeadersOptions configures which security headers are set by the SecurityHeaders middleware.

type ServerSettings

type ServerSettings struct {
	// Host specifies the hostname or IP address where the server will be hosted.
	Host string

	// Port is the port number on which the server listens for incoming requests.
	Port int

	// ReadTimeout is the maximum duration for reading the entire request.
	ReadTimeout time.Duration

	// WriteTimeout is the maximum duration before timing out writes of the response.
	WriteTimeout time.Duration

	// IdleTimeout is the maximum time to wait for the next request when keep-alives are enabled.
	IdleTimeout time.Duration

	// ShutdownTimeout is how long to wait for in-flight requests to complete
	// during graceful shutdown.
	ShutdownTimeout time.Duration
}

ServerSettings defines the configuration for a server, including host, port, timeouts, and graceful shutdown settings.

type Service

type Service interface {
	// Name returns the unique identifier for this service (e.g., "gas/auth").
	Name() string

	// Init initializes the service. Called automatically by the DI container
	// after construction. Services register routes, middleware, migrations,
	// and event subscriptions here.
	Init() error

	// Close gracefully shuts down the service. Called at App shutdown
	// (singletons) or when a Scope is closed (scoped services).
	Close() error
}

Service is the core interface for lifecycle-managed services. Any type registered with the DI container that implements this interface will have Init() called after construction and Close() called at shutdown (singletons) or scope end (scoped). This holds however the value was registered: a pre-built instance passed to WithServiceInstance is initialized too. Transient services cannot implement this interface — registration will be rejected.

Implementing it partially is rejected too. Init and Close are the managed lifecycle, so a type declaring either one must implement all three. A type that declares one and stops is not a Service and would be silently inert: Init never runs, Close never runs, and the kill switch cannot see it. BuildAll returns an error naming the missing or mis-typed methods instead.

Name is not a trigger. Declaring only Name leaves a type an ordinary dependency, and the same registration options carry those — loggers, config providers, per-request values — which must keep working.

A type from another package that declares Close (an io.Closer such as *sql.DB) cannot be given the remaining methods; wrap it in a service of your own rather than registering it directly.

type ServiceContainer

type ServiceContainer struct {
	// contains filtered or unexported fields
}

ServiceContainer is a dependency injection container that manages service registration, construction, and lifetime-scoped resolution.

func NewServiceContainer

func NewServiceContainer() *ServiceContainer

NewServiceContainer creates a new ServiceContainer.

func (*ServiceContainer) BuildAll

func (c *ServiceContainer) BuildAll() error

BuildAll initializes pre-registered Service instances, then eagerly resolves all singleton services in dependency order. Transient and scoped services are validated but not constructed.

Implementing Service means the container manages the lifecycle, so a pre-built instance is initialized like any other service. It is initialized before anything the container constructs, matching the order it became available, so reversing that order at shutdown stays correct.

func (*ServiceContainer) CanResolve

func (c *ServiceContainer) CanResolve(t reflect.Type) bool

CanResolve reports whether the container has an instance or registration that can satisfy the given type (including interface matching).

func (*ServiceContainer) EachInstance

func (c *ServiceContainer) EachInstance(fn func(reflect.Value))

EachInstance iterates all built singleton instances (including pre-registered ones) in the order they became available. A dependency is always constructed and cached before the constructor that consumes it returns, so this order is a valid initialization order: reversing it is safe for shutdown.

func (*ServiceContainer) MustResolve added in v0.4.0

func (c *ServiceContainer) MustResolve(i any) any

MustResolve is like Resolve but panics if the service cannot be resolved.

func (*ServiceContainer) NewScope

func (c *ServiceContainer) NewScope() *Scope

NewScope creates a scoped resolution context. Scoped services resolved within this scope share instances; singletons delegate to the container.

func (*ServiceContainer) RegisterScopedService added in v0.4.0

func (c *ServiceContainer) RegisterScopedService(i, ctor any)

RegisterScopedService registers a constructor for the type of i with the Scoped lifetime. i is a type token, typically TypePtr[T]().

func (*ServiceContainer) RegisterService added in v0.4.0

func (c *ServiceContainer) RegisterService(i, ctor any, lifetime ServiceLifetime)

RegisterService registers a constructor for the type of i with the given lifetime. i is a type token, typically TypePtr[T](). See RegisterCtor for the accepted constructor signatures and lifetime restrictions.

func (*ServiceContainer) RegisterServiceInstance added in v0.4.0

func (c *ServiceContainer) RegisterServiceInstance(val any)

RegisterServiceInstance registers a pre-built value under its dynamic type. Treated as a singleton.

func (*ServiceContainer) RegisterSingletonService added in v0.4.0

func (c *ServiceContainer) RegisterSingletonService(i, ctor any)

RegisterSingletonService registers a constructor for the type of i with the Singleton lifetime. i is a type token, typically TypePtr[T]().

func (*ServiceContainer) RegisterTransientService added in v0.4.0

func (c *ServiceContainer) RegisterTransientService(i, ctor any)

RegisterTransientService registers a constructor for the type of i with the Transient lifetime. i is a type token, typically TypePtr[T]().

func (*ServiceContainer) Resolve added in v0.4.0

func (c *ServiceContainer) Resolve(i any) (any, error)

Resolve retrieves or builds the service registered for the type of i. i is a type token, typically TypePtr[T]().

type ServiceLifetime

type ServiceLifetime uint8

ServiceLifetime controls how a service instance is created and cached.

const (
	// ServiceLifetimeSingleton services are created once and shared across all consumers.
	ServiceLifetimeSingleton ServiceLifetime = iota
	// ServiceLifetimeScoped services are created once per Scope.
	ServiceLifetimeScoped
	// ServiceLifetimeTransient services are created fresh on every resolution.
	ServiceLifetimeTransient
)

func (ServiceLifetime) String

func (l ServiceLifetime) String() string

type StorageObject

type StorageObject struct {
	Body        io.ReadCloser
	Metadata    map[string]string
	ContentType string
	Size        int64
}

StorageObject holds the response from a Download call, including the body and any metadata the backend provides (content type, size, etc.).

type StorageOption

type StorageOption func(*storageOptions)

StorageOption configures a storage operation.

func InBucket

func InBucket(name string) StorageOption

InBucket overrides the default bucket for a single operation.

func WithContentType

func WithContentType(ct string) StorageOption

WithContentType sets the content type for an Upload.

func WithMetadata

func WithMetadata(m map[string]string) StorageOption

WithMetadata attaches provider-specific key-value metadata to an Upload.

type StorageProvider

type StorageProvider interface {
	Upload(ctx context.Context, key string, data io.Reader, opts ...StorageOption) error
	Download(ctx context.Context, key string, opts ...StorageOption) (*StorageObject, error)
	Delete(ctx context.Context, key string, opts ...StorageOption) error
	Head(ctx context.Context, key string, opts ...StorageOption) (*ObjectInfo, error)
	PresignDownloadURL(ctx context.Context, key string, expires time.Duration, opts ...StorageOption) (string, error)
	PresignUploadURL(ctx context.Context, key string, expires time.Duration, opts ...StorageOption) (string, error)
}

StorageProvider abstracts file storage (S3, DO Spaces, local filesystem, etc.).

type SystemAllServicesInitializedPayload

type SystemAllServicesInitializedPayload struct{}

SystemAllServicesInitializedPayload is an empty payload for the all-services-initialized event.

type SystemServerShuttingDownPayload

type SystemServerShuttingDownPayload struct{}

SystemServerShuttingDownPayload is an empty payload for the server-shutting-down event.

type SystemServiceClosedPayload

type SystemServiceClosedPayload struct {
	ServiceName string
}

SystemServiceClosedPayload carries the name of the closed service.

type SystemServiceInitializedPayload

type SystemServiceInitializedPayload struct {
	ServiceName string
}

SystemServiceInitializedPayload carries the name of the initialized service.

type SystemShuttingDownPayload

type SystemShuttingDownPayload struct{}

SystemShuttingDownPayload is an empty payload for the shutting-down event.

type TemplateProvider

type TemplateProvider interface {
	// Get returns the raw template content by name.
	Get(ctx context.Context, name string) ([]byte, error)

	// List returns all available template names.
	List(ctx context.Context) ([]string, error)

	// Register adds or replaces a template by name and raw content.
	Register(ctx context.Context, name string, content []byte) error

	// RegisterFS walks an fs.FS and registers every template file found.
	RegisterFS(ctx context.Context, fsys fs.FS) error
}

TemplateProvider abstracts template storage and retrieval. Implementations may be backed by a filesystem, database, memory, or any combination. Consumers such as UIProvider (HTML rendering) and EmailProvider (email templating) resolve raw template content through this interface.

type TemplatedEmail

type TemplatedEmail struct {
	SubjectTemplate string
	TextTemplate    string
	HTMLTemplate    string
	Data            any
	Email
}

TemplatedEmail represents an email message where templates and data define subject, text, and HTML bodies.

type UIProvider

type UIProvider interface {
	Render(w http.ResponseWriter, name string, data any) error
	RenderFragment(w http.ResponseWriter, name string, data any) error // renders without layout wrapper, useful for HTMX
	RenderWithStatus(w http.ResponseWriter, status int, name string, data any) error
	RegisterFuncs(funcs template.FuncMap)
}

UIProvider abstracts template rendering. Implemented by gas/ui or any other UI service. Template storage and retrieval is handled by TemplateProvider; UIProvider focuses on compilation and rendering.

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

Worker manages service lifecycle, dependency injection, and the event bus without an HTTP server. Use it for non-HTTP environments such as AWS Lambda, background job processors, or CLI tools. For HTTP servers, use App which embeds Worker and adds routing, CSRF protection, and an HTTP listener.

func NewWorker

func NewWorker(opts ...Option) *Worker

NewWorker creates a Worker with the given options. Only WorkerOption values are applied; passing an AppOption panics.

func (*Worker) ActiveServices

func (w *Worker) ActiveServices() []string

ActiveServices returns the names of all currently active services.

func (*Worker) ActiveServicesMap

func (w *Worker) ActiveServicesMap() map[string]Service

ActiveServicesMap returns a map of all currently active services. This is a test helper.

func (*Worker) CheckHealth

func (w *Worker) CheckHealth(ctx context.Context) map[string]error

CheckHealth runs CheckHealth concurrently on every active service that implements HealthReporter and returns a map of service name to result (nil if healthy). Services that do not implement HealthReporter are omitted.

func (*Worker) CheckReady

func (w *Worker) CheckReady(ctx context.Context) map[string]error

CheckReady runs CheckReady concurrently on every active service that implements ReadyReporter and returns a map of service name to result (nil if ready). Services that do not implement ReadyReporter are omitted.

func (*Worker) CloseService

func (w *Worker) CloseService(name string) error

CloseService performs the kill-switch sequence for a single service at runtime. Infrastructure is cleaned up first so that even if Close() panics or fails, routes and subscriptions are already removed.

func (*Worker) ConfigProvider

func (w *Worker) ConfigProvider() ConfigProvider

ConfigProvider resolves the ConfigProvider from the DI container. Returns nil if no ConfigProvider is registered.

func (*Worker) EventBus

func (w *Worker) EventBus() *EventBus

EventBus returns the Worker's event bus.

func (*Worker) InitServices

func (w *Worker) InitServices() (err error)

InitServices builds all singletons via the DI container (which calls Init() on each Service automatically), then collects Service instances for runtime management. Collection follows the container's instance order, so serviceOrder is a genuine initialization order and Shutdown can reverse it safely.

func (*Worker) MigrationManager

func (w *Worker) MigrationManager() MigrationManager

MigrationManager resolves the MigrationManager from the DI container. Returns nil if no MigrationManager is registered.

func (*Worker) RegisterScopedService added in v0.4.0

func (w *Worker) RegisterScopedService(t, ctor any)

RegisterScopedService registers a constructor for the type of t with the Scoped lifetime on the worker's service container. t is a type token, typically TypePtr[T]().

func (*Worker) RegisterService added in v0.4.0

func (w *Worker) RegisterService(t, ctor any, lifetime ServiceLifetime)

RegisterService registers a constructor for the type of t with the given lifetime on the worker's service container. t is a type token, typically TypePtr[T]().

func (*Worker) RegisterServiceInstance added in v0.4.0

func (w *Worker) RegisterServiceInstance(val any)

RegisterServiceInstance registers a pre-built value on the worker's service container under its dynamic type. Treated as a singleton.

func (*Worker) RegisterSingletonService added in v0.4.0

func (w *Worker) RegisterSingletonService(t, ctor any)

RegisterSingletonService registers a constructor for the type of t with the Singleton lifetime on the worker's service container. t is a type token, typically TypePtr[T]().

func (*Worker) RegisterTransientService added in v0.4.0

func (w *Worker) RegisterTransientService(t, ctor any)

RegisterTransientService registers a constructor for the type of t with the Transient lifetime on the worker's service container. t is a type token, typically TypePtr[T]().

func (*Worker) RestartService

func (w *Worker) RestartService(name string) error

RestartService re-initializes a previously closed service. The service must have been registered with the Worker at construction time and built during InitServices (i.e., it must be a singleton in serviceOrder).

func (*Worker) Run

func (w *Worker) Run() error

Run initializes services, runs migrations and ready hooks, then blocks until a SIGINT or SIGTERM signal is received before shutting down. For non-blocking lifecycle control, use Start and Shutdown directly.

func (*Worker) ServiceContainer

func (w *Worker) ServiceContainer() *ServiceContainer

ServiceContainer returns the Worker's dependency injection container.

func (*Worker) Shutdown

func (w *Worker) Shutdown() error

Shutdown emits SystemShuttingDown and closes all services in reverse initialization order. Safe to call multiple times (subsequent calls are no-ops once services are closed).

func (*Worker) Start

func (w *Worker) Start() error

Start initializes all services, runs pending migrations, and executes ready hooks. It does NOT block — use it when you need explicit lifecycle control (e.g. AWS Lambda). Call Shutdown when done.

type WorkerOption

type WorkerOption func(*Worker)

WorkerOption configures a Worker. It is the base option type for DI registration, ready hooks, and other non-HTTP concerns. Both NewWorker and NewApp accept WorkerOption values.

func WithReadyFunc

func WithReadyFunc(fn func(*ServiceContainer) error) WorkerOption

WithReadyFunc registers a function that runs after all services are initialized and migrations are applied but before Run blocks or Start returns. Multiple funcs are called in registration order; any error aborts startup.

func WithScopedService

func WithScopedService[T any](ctor any) WorkerOption

WithScopedService registers a service constructor with a scoped lifetime.

func WithService

func WithService[T any](ctor any, lifetime ServiceLifetime) WorkerOption

WithService registers a constructor-based service with the given lifetime.

func WithServiceInstance

func WithServiceInstance[T any](val T) WorkerOption

WithServiceInstance registers a pre-built service instance (singleton).

Pre-built means pre-constructed, not pre-initialized: if the value implements Service, the container calls Init() on it during InitServices like any other service, and Close() at shutdown. Implementing Service is what hands the lifecycle to the container, however the value got there. Do not call Init() yourself before registering, or it runs twice.

func WithSingletonService

func WithSingletonService[T any](ctor any) WorkerOption

WithSingletonService registers a singleton service constructor.

func WithTransientService

func WithTransientService[T any](ctor any) WorkerOption

WithTransientService registers a transient service constructor.

Directories

Path Synopsis
auth module
cache module
config module
database module
email module
log module
migrate module
queue module
storage module
template module
ui module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL