Documentation
¶
Overview ¶
Package dygo is a modular application framework for Go.
An application is a tree of modules. Each module declares providers (constructor functions or values), the tokens it exports, and the modules it imports. At startup the container reads constructor signatures, checks the whole dependency graph, builds every singleton in dependency order, and runs lifecycle hooks module level by module level.
var Module = &dygo.Module{
Name: "users",
Imports: []*dygo.Module{database.Module},
Providers: []dygo.Provider{
dygo.Provide(NewUserService),
dygo.Provide(NewPgRepo, dygo.As[Repo]()),
},
Exports: []dygo.Token{dygo.TypeOf[*UserService]()},
}
app, err := dygo.CreateApplicationContext(ctx, AppModule)
Go has no decorators, so providers, injection and modules are declared with plain function calls and struct literals. Reflection is used only while the application boots.
Writing a transport ¶
Transport, Endpoint, Pipeline, Enhancers, NewEnhancers and ProvidersImplementing are for packages that add a way to receive work, such as the ws, graphql and micro modules. Applications do not need them.
Stability ¶
From v1.0.0 the module follows semantic versioning. The promise covers the exported Go API, the JSON error body written by errs.DefaultWriter, and the struct tag syntax read by the binder (path, query, header, cookie, form, file, body, default, required). It does not cover the output format of the dygo CLI, the layout of generated code, or log messages.
Index ¶
- Constants
- Variables
- func Construct[T any](ref *ModuleRef, ctor any) (T, error)
- func Discover[T any](ref *ModuleRef) []T
- func Get[T any](r Resolver) (T, error)
- func GetNamed[T any](r Resolver, tok NamedToken[T]) (T, error)
- func MustGet[T any](r Resolver) T
- func ProvidersImplementing[T any](a *App) []T
- func Ref[T any]() any
- func RefToken(tok Token) any
- func RegisterConverter[T any](fn func(string) (T, error))
- func RegisterSource(name string, fn func(*http.Request) (any, error))
- func Request(ctx context.Context) *http.Request
- func ResponseStatus(ctx context.Context) int
- func SetCookie(ctx context.Context, c *http.Cookie)
- func SetHeader(ctx context.Context, key, value string)
- func SetStatus(ctx context.Context, code int)
- func Stopping(ctx context.Context) <-chan struct{}
- func WithDurableContext(ctx context.Context, id string, payload any) context.Context
- type App
- func (a *App) Close(ctx context.Context) error
- func (a *App) ConnectMicroservice(t Transport)
- func (a *App) Controllers() []any
- func (a *App) DurableContexts() []string
- func (a *App) Get(tok Token) (any, error)
- func (a *App) Providers() []ProviderInfo
- func (a *App) ReleaseContext(ctx context.Context, id string) error
- func (a *App) Run(ctx context.Context) error
- func (a *App) Shutdown(ctx context.Context, signal string) error
- func (a *App) StartAllMicroservices(ctx context.Context) error
- func (a *App) WithRequestScope(ctx context.Context, r *http.Request) (_ context.Context, end func())
- type ArgumentsHost
- type BeforeApplicationShutdown
- type BindError
- type BootError
- type CallHandler
- type ContextID
- type ContextIDStrategy
- type ContextKind
- type Controller
- type Endpoint
- type Enhancers
- type ExceptionFilter
- type ExecutionContext
- type FieldError
- type FilterFunc
- type GraphDep
- type GraphInfo
- type GraphModule
- type GraphProvider
- type GraphQLHost
- type Guard
- type GuardFunc
- type HTTPApp
- type HTTPHost
- type Handler
- type In
- type Interceptor
- type InterceptorFunc
- type Lazy
- type Middleware
- type Module
- type ModuleRef
- type NamedToken
- type NoContent
- type OnApplicationBootstrap
- type OnApplicationShutdown
- type OnModuleDestroy
- type OnModuleInit
- type OnRequestEnd
- type Option
- func WithBodyLimit(n int64) Option
- func WithContextIDStrategy(s ContextIDStrategy) Option
- func WithErrorWriter(w errs.Writer) Option
- func WithGlobalFilters(f ...any) Option
- func WithGlobalGuards(g ...any) Option
- func WithGlobalInterceptors(i ...any) Option
- func WithGlobalPipes(p ...any) Option
- func WithGlobalPrefix(prefix string, exclude ...string) Option
- func WithHTTPServer(fn func(*http.Server)) Option
- func WithLogger(l *slog.Logger) Option
- func WithMiddleware(mw ...Middleware) Option
- func WithOverride(tok Token, p Provider) Option
- func WithShutdownTimeout(d time.Duration) Option
- func WithVersioning(v VersioningOptions) Option
- type Optional
- type PanicError
- type Pipe
- type PipeFunc
- type Pipeline
- type ProvideOption
- type Provider
- type ProviderInfo
- type RPCHost
- type RawJSON
- type Resolver
- type Responder
- type RouteInfo
- type RouteOption
- func BodyLimit(n int64) RouteOption
- func Filters(f ...any) RouteOption
- func Guards(g ...any) RouteOption
- func HTTPCode(code int) RouteOption
- func Header(key, value string) RouteOption
- func Interceptors(i ...any) RouteOption
- func Options(opts ...RouteOption) RouteOption
- func Pipes(p ...any) RouteOption
- func Redirect(url string, code int) RouteOption
- func RouteMiddleware(mw ...Middleware) RouteOption
- func SetMetadata(key, value any) RouteOption
- func StrictJSON() RouteOption
- func Version(v ...string) RouteOption
- type Router
- type SSEEvent
- type Scope
- type Scoped
- type Token
- type Transport
- type VersioningOptions
- func CustomVersioning(extract func(*http.Request) []string, defaultVersion ...string) VersioningOptions
- func HeaderVersioning(header string, defaultVersion ...string) VersioningOptions
- func MediaTypeVersioning(key string, defaultVersion ...string) VersioningOptions
- func URIVersioning(defaultVersion ...string) VersioningOptions
- type WSHost
Constants ¶
const ( AppGuards = "dygo.app.guards" AppInterceptors = "dygo.app.interceptors" AppPipes = "dygo.app.pipes" AppFilters = "dygo.app.filters" )
Group names that make providers global enhancers with dependency injection:
dygo.Provide(NewAuthGuard, dygo.Group(dygo.AppGuards))
Members must be singletons. They run after the enhancers given with the WithGlobal* options.
const VersionNeutral = "*"
VersionNeutral as a route version makes the route match every version, and requests without a version.
Variables ¶
var ( // ErrNotFound is returned by Get when no provider matches the token. ErrNotFound = errors.New("dygo: no provider found") // ErrAmbiguous is returned by Get when more than one provider matches. ErrAmbiguous = errors.New("dygo: more than one provider found") )
var ErrFiltered = errors.New("dygo: error handled by an exception filter")
ErrFiltered is returned by Pipeline.Run when an exception filter caught the error. The filter has answered (or chosen not to), so the transport sends nothing more.
var ErrInspected = errors.New("dygo: inspection written to DYGO_INSPECT_OUT; the application was not started")
ErrInspected is returned by Create and CreateApplicationContext when the DYGO_INSPECT_OUT environment variable is set: the dygo CLI (dygo routes, graph and check) runs the application that way to read its graph and routes, and the application is not started.
Functions ¶
func Discover ¶
Discover is ProvidersImplementing for a provider: it returns every built singleton in the application that implements or is of type T. Call it from OnModuleInit or later, when every singleton is built. Packages such as events and schedule use it to find their listeners and jobs.
func GetNamed ¶
func GetNamed[T any](r Resolver, tok NamedToken[T]) (T, error)
GetNamed resolves a named token from r.
func ProvidersImplementing ¶
ProvidersImplementing returns every built singleton whose instance implements or is of type T. Transports use it to find their handlers (controllers, gateways, message handlers). Code inside a provider uses Discover.
func Ref ¶
Ref refers to the provider registered under T, as an enhancer argument:
r = r.Use(dygo.Guards(dygo.Ref[*auth.Guard]()))
func RegisterConverter ¶
RegisterConverter makes T usable in path, query, header, cookie and form fields. Types implementing encoding.TextUnmarshaler need no registration. Call it before creating the application, for example in an init function.
func RegisterSource ¶
RegisterSource adds a custom input tag. A field tagged name:"" receives fn's result:
dygo.RegisterSource("user", func(r *http.Request) (any, error) { return auth.UserFrom(r) })
type In struct {
User *auth.User `user:""`
}
Call it before creating the application.
func ResponseStatus ¶
ResponseStatus returns the status set with SetStatus during this request, or 0. Interceptors use it to see the real status of a successful response, for example a health check that answers 503.
func SetStatus ¶
SetStatus overrides the success status code from inside a handler. To fix it for the whole route, use the HTTPCode route option.
func Stopping ¶
Stopping returns a channel that is closed when the server starts shutting down. Long running handlers, such as an SSE stream waiting for events, should select on it so that shutdown does not wait for the shutdown timeout. It returns nil (a channel that never closes) outside a request.
func WithDurableContext ¶
WithDurableContext returns ctx carrying a context ID, so that Scoped.Get resolves durable providers for it outside an HTTP request: in a queue job, an event listener or a microservice handler. Plain request scoped providers cannot be resolved with it. Inside a request it takes precedence over the strategy.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is a running application context: every singleton is built and every startup hook has run.
func CreateApplicationContext ¶
CreateApplicationContext scans the module tree from root, validates the whole dependency graph, builds every singleton, and runs OnModuleInit and OnApplicationBootstrap. Wiring problems are reported together in a *BootError. ctx is passed to constructors that take a context.Context and to the startup hooks.
func (*App) ConnectMicroservice ¶
ConnectMicroservice adds a transport to the application, for a hybrid app. HTTPApp.Listen and App.Run start it with App.StartAllMicroservices if that was not called; shutdown closes it after BeforeApplicationShutdown, together with the HTTP drain.
func (*App) Controllers ¶
Controllers returns every controller instance, in module level order.
func (*App) DurableContexts ¶
DurableContexts lists the context IDs that have durable instances.
func (*App) Get ¶
Get returns the instance for tok. It first looks from the root module, then in every module. It returns ErrNotFound or ErrAmbiguous when the token does not match exactly one provider.
func (*App) Providers ¶
func (a *App) Providers() []ProviderInfo
Providers lists every built singleton provider and controller in build order.
func (*App) ReleaseContext ¶
ReleaseContext drops the durable instances of a context ID, for example when a tenant is deleted, after calling OnApplicationShutdown on them. The next request with the ID builds new ones. Requests still using the old instances keep them.
func (*App) Run ¶
Run blocks until ctx is cancelled or the process receives SIGINT or SIGTERM, then shuts the application down within the shutdown timeout. Connected transports are started first (see App.ConnectMicroservice).
func (*App) Shutdown ¶
Shutdown runs OnModuleDestroy, BeforeApplicationShutdown and OnApplicationShutdown on every initialized instance, in reverse startup order. It is safe to call more than once; later calls do nothing.
func (*App) StartAllMicroservices ¶
StartAllMicroservices starts every connected transport that is not running. When one fails, the ones it started are closed again and the error is returned.
func (*App) WithRequestScope ¶
func (a *App) WithRequestScope(ctx context.Context, r *http.Request) (_ context.Context, end func())
WithRequestScope returns ctx carrying a new request scope, for a transport whose unit of work is not an HTTP request, such as a WebSocket message: Scoped.Get with the returned context builds request scoped providers once for that unit, and end calls their OnRequestEnd hooks. Call end when the work is done, from the goroutine that did it. r is the request the work belongs to (the WebSocket handshake, for example), or nil; it is what request scoped providers get for *http.Request and what the ContextIDStrategy reads, so durable providers resolve for its context ID. The new scope takes precedence over the scope of a request ctx already belongs to; WithDurableContext still takes precedence over both.
type ArgumentsHost ¶
type ArgumentsHost interface {
// Kind is the transport: KindHTTP, KindRPC or KindWS.
Kind() ContextKind
// HTTP returns the request and response. It is nil unless Kind is KindHTTP.
HTTP() HTTPHost
// RPC returns the message of a microservice handler. It is nil unless
// Kind is KindRPC.
RPC() RPCHost
// WS returns the client and message of a WebSocket gateway handler. It is
// nil unless Kind is KindWS.
WS() WSHost
// GraphQL returns the field being resolved. It is nil unless Kind is
// KindGraphQL.
GraphQL() GraphQLHost
// Context is the request context.
Context() context.Context
}
ArgumentsHost is what an exception filter receives.
type BeforeApplicationShutdown ¶
type BeforeApplicationShutdown interface {
BeforeApplicationShutdown(ctx context.Context, signal string) error
}
BeforeApplicationShutdown runs after every OnModuleDestroy. signal is "SIGINT" or "SIGTERM" when shutdown came from a signal, and empty otherwise.
type BindError ¶
BindError is returned (and written as a 400) when request input does not fit the handler's input struct. It lists every failing field.
type BootError ¶
type BootError struct {
Problems []error
}
BootError lists every problem found while building the application. It is returned by CreateApplicationContext instead of stopping at the first one.
type CallHandler ¶
CallHandler runs the rest of the pipeline: later interceptors, pipes and the handler. Handle may be called more than once, for a retry.
type ContextID ¶
type ContextID struct {
// ID is the context ID returned by the strategy, "" for none.
ID string
// Payload is the strategy's payload from the first request with this
// ID.
Payload any
}
ContextID is the durable context a durable provider is built for. Durable providers inject it to learn their tenant:
func NewTenantDB(cid dygo.ContextID, pools *Pools) (*TenantDB, error) {
return pools.Open(cid.ID)
}
In a plain request scoped provider it holds the request's ID and payload, or nothing when there is no strategy.
type ContextIDStrategy ¶
ContextIDStrategy returns the context ID of a request. An empty id means the request has none: durable providers then behave like plain request scoped ones for it.
type ContextKind ¶
type ContextKind string
ContextKind names the transport an ArgumentsHost belongs to.
const ( KindHTTP ContextKind = "http" KindRPC ContextKind = "rpc" KindWS ContextKind = "ws" KindGraphQL ContextKind = "graphql" )
The transports. KindHTTP is a controller route; KindRPC a microservice pattern (package micro); KindWS a WebSocket gateway message (package ws); KindGraphQL a query, mutation or subscription field (package graphql).
type Controller ¶
type Controller interface {
Routes(r Router)
}
Controller is implemented by every type listed in a module's Controllers. Routes is called once at startup to register the controller's routes.
type Endpoint ¶
type Endpoint struct {
// Kind is KindRPC, KindWS or KindGraphQL. For KindGraphQL, the msg given
// to Run must implement [GraphQLHost].
Kind ContextKind
// Pattern is the subject, method or event the handler serves.
Pattern string
// Owner is the provider that declared the handler. Refs and enhancer
// constructors resolve from its module.
Owner any
// Handler is the handler function name, for errors and [RouteInfo].
Handler string
// Input and Output are the handler's types, nil when it has none.
Input, Output reflect.Type
// ControllerOptions apply to every handler of the owner, like Router.Use;
// Options to this handler only. Only enhancers and metadata are used.
ControllerOptions, Options []RouteOption
}
Endpoint describes one transport handler for Enhancers.Pipeline.
type Enhancers ¶
type Enhancers struct {
// contains filtered or unexported fields
}
Enhancers resolves the enhancers of one transport's handlers.
func NewEnhancers ¶
func NewEnhancers(a *App, global []RouteOption, inheritApp bool) (*Enhancers, error)
NewEnhancers prepares pipelines for a transport. global holds the transport's own global enhancers (dygo.Guards(...) and so on). With inheritApp the app wide ones apply too: the WithGlobal* options and the App* groups, for hybrid apps. Without it a transport does not run the HTTP app's global enhancers, which often read HTTPHost.
type ExceptionFilter ¶
type ExceptionFilter interface {
Catch(err error, host ArgumentsHost) bool
}
ExceptionFilter handles errors. Catch returns true when it wrote the response; false passes the error to the next filter and in the end to the error writer.
func FilterFor ¶
func FilterFor[E error](fn func(e E, host ArgumentsHost)) ExceptionFilter
FilterFor returns a filter for errors of type E, matched with errors.As, like @Catch(SomeException):
dygo.FilterFor(func(e *store.ConflictError, host dygo.ArgumentsHost) {
errs.WriteJSON(host.HTTP().Response(), errs.ToBody(errs.Conflict(e.Error())))
})
type ExecutionContext ¶
type ExecutionContext interface {
ArgumentsHost
// SetContext replaces the request context seen by later stages and the
// handler. ctx must be derived from Context().
SetContext(ctx context.Context)
// Route describes the matched route.
Route() RouteInfo
// Controller is the type of the controller that declared the route.
Controller() reflect.Type
// RouteMetadata returns the value set with [SetMetadata] on the route.
RouteMetadata(key any) (any, bool)
// ControllerMetadata returns the value set with [SetMetadata] through
// Router.Use, that is for the whole controller.
ControllerMetadata(key any) (any, bool)
// MergedMetadata returns the controller's and the route's values joined,
// controller first, when both are slices of the same type (joined once at
// startup; do not modify it). Otherwise it returns the route's value, or
// else the controller's.
MergedMetadata(key any) (any, bool)
}
ExecutionContext describes the request being handled, for guards, interceptors and pipes.
It is not itself a context.Context: call Context to pass the request context on, and SetContext to change it for later stages and the handler (for example to add the authenticated user).
type FieldError ¶
type FieldError = binder.FieldError
FieldError is one failing field in a BindError.
type FilterFunc ¶
type FilterFunc func(err error, host ArgumentsHost) bool
FilterFunc is a function used as an ExceptionFilter.
func (FilterFunc) Catch ¶
func (f FilterFunc) Catch(err error, host ArgumentsHost) bool
Catch calls f.
type GraphDep ¶
type GraphDep struct {
Token string `json:"token"`
Kind string `json:"kind,omitempty"`
Providers []int `json:"providers,omitempty"`
}
GraphDep is one dependency of a provider. Kind is empty for a plain dependency, otherwise optional, lazy, scoped, group, context, moduleRef or request. Providers lists the provider IDs it resolved to: one for a token, every member for a group, none when an optional dependency is missing.
type GraphInfo ¶
type GraphInfo struct {
Modules []GraphModule `json:"modules"`
Providers []GraphProvider `json:"providers"`
}
GraphInfo describes a module graph: every module and every provider with its resolved dependencies. Describe returns it, and the dygo CLI renders it (dygo graph) and checks it (dygo check).
func Describe ¶
Describe scans and validates the module tree from root, like CreateApplicationContext, without building anything. It is the cheapest wiring check, for a unit test or CI:
func TestWiring(t *testing.T) {
if _, err := dygo.Describe(app.Module); err != nil {
t.Fatal(err)
}
}
On a *BootError the graph is still returned when the module tree could be scanned, with unresolved dependencies left empty.
type GraphModule ¶
type GraphModule struct {
Name string `json:"name"`
Path string `json:"path"`
Global bool `json:"global,omitempty"`
Level int `json:"level"`
Imports []int `json:"imports,omitempty"`
Exports []string `json:"exports,omitempty"`
}
GraphModule is one module. Imports holds indexes into GraphInfo.Modules. The root module is first.
type GraphProvider ¶
type GraphProvider struct {
ID int `json:"id"`
Module int `json:"module"`
Type string `json:"type"`
Tokens []string `json:"tokens,omitempty"`
Source string `json:"source"`
Scope string `json:"scope"`
Controller bool `json:"controller,omitempty"`
Groups []string `json:"groups,omitempty"`
Deps []GraphDep `json:"deps,omitempty"`
}
GraphProvider is one provider or controller. ID is its index in GraphInfo.Providers and Module its module's index in GraphInfo.Modules.
type GraphQLHost ¶
type GraphQLHost interface {
// Field is the root field as "Type.field", such as "Query.user".
Field() string
// Args are the field's arguments.
Args() map[string]any
// Request is the HTTP request that carried the operation, nil for a
// subscription over a WebSocket after the handshake.
Request() *http.Request
}
GraphQLHost gives access to the GraphQL field being resolved.
type Guard ¶
type Guard interface {
CanActivate(ec ExecutionContext) (bool, error)
}
Guard decides whether a request may go on. Returning false gives a 403; returning an error sends that error to the filters, so a guard can return errs.Unauthorized for a 401.
type GuardFunc ¶
type GuardFunc func(ec ExecutionContext) (bool, error)
GuardFunc is a function used as a Guard.
func (GuardFunc) CanActivate ¶
func (f GuardFunc) CanActivate(ec ExecutionContext) (bool, error)
CanActivate calls f.
type HTTPApp ¶
type HTTPApp struct {
*App
// contains filtered or unexported fields
}
HTTPApp is an application with an HTTP server. It embeds the application context, so Get, Providers and Shutdown work as on App.
func Create ¶
Create builds the application like CreateApplicationContext, then asks every controller for its routes and compiles them. Route problems (bad patterns, duplicates, conflicts, bad handler input types) are reported in the same *BootError as wiring problems, before any startup hook runs.
func (*HTTPApp) Handler ¶
Handler returns the application's http.Handler, for httptest or a server you manage yourself.
func (*HTTPApp) Listen ¶
Listen serves on addr until ctx is cancelled or the process gets SIGINT or SIGTERM, then shuts down: OnModuleDestroy, BeforeApplicationShutdown, drain in-flight requests, OnApplicationShutdown. It returns nil after a clean shutdown.
type HTTPHost ¶
type HTTPHost interface {
Request() *http.Request
Response() http.ResponseWriter
}
HTTPHost gives access to the HTTP request and response.
type Handler ¶
type Handler interface {
// contains filtered or unexported methods
}
Handler is a compiled route handler. Create one with Handle, HandleNoInput or HandleRaw.
func Handle ¶
Handle turns a typed function into a route handler. In is a struct whose tagged fields are filled from the request:
type GetUserIn struct {
ID uuid.UUID `path:"id"`
Expand bool `query:"expand"`
Tenant string `header:"X-Tenant" required:"true"`
Body CreateDTO `body:"json"`
}
Tags: path, query, header, cookie, form, file, body ("json", "form" or "raw"), plus default:"..." and required:"true". Out is written as JSON unless it is a Responder, NoContent, string or []byte. A returned error is written by the error writer; see package errs.
The binding plan is compiled when routes are built, so a bad In type is a startup error.
func HandleNoInput ¶
HandleNoInput is Handle for a handler that needs nothing from the request.
type In ¶
type In struct{}
In marks a parameter struct. Embed it in a struct used as a constructor parameter and the container fills each exported field:
type Params struct {
dygo.In
DB *sql.DB `name:"db.primary"`
Cache Cache `optional:"true"`
Plugins []Plugin `group:"plugins"`
}
Supported tags: name (named token, see NewToken), optional ("true" gives the zero value when nothing is provided) and group (a slice of group members).
type Interceptor ¶
type Interceptor interface {
Intercept(ec ExecutionContext, next CallHandler) (any, error)
}
Interceptor wraps the handler. Work before next.Handle runs before the handler; the result can be changed, replaced or mapped to another error afterwards:
func (Envelope) Intercept(ec dygo.ExecutionContext, next dygo.CallHandler) (any, error) {
out, err := next.Handle()
if err != nil {
return nil, err
}
return map[string]any{"data": out}, nil
}
type InterceptorFunc ¶
type InterceptorFunc func(ec ExecutionContext, next CallHandler) (any, error)
InterceptorFunc is a function used as an Interceptor.
func (InterceptorFunc) Intercept ¶
func (f InterceptorFunc) Intercept(ec ExecutionContext, next CallHandler) (any, error)
Intercept calls f.
type Lazy ¶
type Lazy[T any] struct { // contains filtered or unexported fields }
Lazy defers resolving a dependency until Get is called, which is how a dependency cycle is broken. Do not call Get inside a constructor; call it later, for example in OnModuleInit or in a request handler.
type Middleware ¶
Middleware is standard net/http middleware, so any existing Go middleware works.
type Module ¶
type Module struct {
// Name identifies the module in error messages. Required.
Name string
// Global makes the module's exports visible to every module without an
// import.
Global bool
// Imports are modules whose exports this module can inject.
Imports []*Module
// Providers are constructed by the container and visible inside the module.
Providers []Provider
// Controllers are built like singleton providers but cannot be injected or
// exported. The HTTP layer (phase 2) registers their routes.
Controllers []Provider
// Exports lists the tokens other modules may inject. A module can export
// its own providers or re-export tokens it imports.
Exports []Token
}
Module groups providers. Declare modules as package level pointers so that importing the same module from several places shares one set of instances:
var Module = &dygo.Module{Name: "users", ...}
A dynamic module (the ForRoot pattern) is a function that returns a new *Module:
func ForRoot(opts Options) *dygo.Module {
return &dygo.Module{
Name: "config",
Global: true,
Providers: []dygo.Provider{dygo.Value(opts), dygo.Provide(NewService)},
Exports: []dygo.Token{dygo.TypeOf[*Service]()},
}
}
type ModuleRef ¶
type ModuleRef struct {
// contains filtered or unexported fields
}
ModuleRef gives a provider access to the container from its own module's point of view. Inject it as a *dygo.ModuleRef constructor parameter.
func (*ModuleRef) App ¶
App returns the application, for packages that discover handlers and build their pipelines with NewEnhancers from a provider (package ws). It is nil while constructors run; call it from OnModuleInit or later.
func (*ModuleRef) Create ¶
Create calls ctor with its parameters resolved from this module, without registering the result. The returned value gets no lifecycle hooks.
type NamedToken ¶
type NamedToken[T any] struct { // contains filtered or unexported fields }
NamedToken is a typed, named token. Two values of the same type can be told apart by name:
var PrimaryDB = dygo.NewToken[*sql.DB]("db.primary")
dygo.Provide(OpenPrimary, dygo.For(PrimaryDB))
Inject it with a field of type *sql.DB tagged name:"db.primary" in a parameter struct that embeds In.
func NewToken ¶
func NewToken[T any](name string) NamedToken[T]
NewToken creates a named token for values of type T. It panics if name is empty, because an empty name would collide with the plain type token.
func (NamedToken[T]) String ¶
func (t NamedToken[T]) String() string
String describes the token in error messages.
type OnApplicationBootstrap ¶
OnApplicationBootstrap runs after every module has been initialized.
type OnApplicationShutdown ¶
type OnApplicationShutdown interface {
OnApplicationShutdown(ctx context.Context, signal string) error
}
OnApplicationShutdown runs last.
type OnModuleDestroy ¶
OnModuleDestroy runs first during shutdown.
type OnModuleInit ¶
OnModuleInit runs once the instance and all its dependencies are built.
type OnRequestEnd ¶
OnRequestEnd is called on every request scoped instance when the request that created it finishes, in reverse creation order. Use it for per-request cleanup such as committing or rolling back a transaction.
type Option ¶
type Option func(*options)
Option configures CreateApplicationContext.
func WithBodyLimit ¶
WithBodyLimit sets the maximum request body size in bytes. The default is 1 MiB; zero or less means no limit. Routes can override it with BodyLimit.
func WithContextIDStrategy ¶
func WithContextIDStrategy(s ContextIDStrategy) Option
WithContextIDStrategy sets how requests map to durable contexts:
dygo.WithContextIDStrategy(func(r *http.Request) (string, any) {
t := r.Header.Get("X-Tenant-ID")
return t, t
})
The ID is trusted as is: validate it (for example against known tenants) in the strategy, since every new ID builds and keeps new instances.
func WithErrorWriter ¶
WithErrorWriter replaces the default error writer (errs.DefaultWriter).
func WithGlobalFilters ¶
WithGlobalFilters adds exception filters to every route. They run after route and controller filters, before the error writer.
func WithGlobalGuards ¶
WithGlobalGuards adds guards to every route, like app.useGlobalGuards. Constructors and Refs resolve from the root module.
func WithGlobalInterceptors ¶
WithGlobalInterceptors adds interceptors to every route.
func WithGlobalPipes ¶
WithGlobalPipes adds pipes to every typed route.
func WithGlobalPrefix ¶
WithGlobalPrefix puts prefix in front of every route. Routes whose path (without the prefix) equals or starts with one of exclude are left alone, for example "/health".
func WithHTTPServer ¶
WithHTTPServer lets you change the *http.Server before it starts: timeouts, TLS, error log. Defaults: ReadHeaderTimeout 10s, IdleTimeout 120s.
func WithLogger ¶
WithLogger sets the logger the framework writes to. By default nothing is logged.
func WithMiddleware ¶
func WithMiddleware(mw ...Middleware) Option
WithMiddleware adds application wide middleware. It wraps everything, including 404 responses. The first middleware is the outermost.
func WithOverride ¶
WithOverride replaces the provider registered under tok, in every module that provides it, with p. The original provider is not built when all of its tokens are overridden. Mostly used by tests; see package dygotest.
func WithShutdownTimeout ¶
WithShutdownTimeout bounds how long App.Run waits for shutdown hooks after a signal. The default is 10 seconds.
func WithVersioning ¶
func WithVersioning(v VersioningOptions) Option
WithVersioning turns on API versioning.
type Optional ¶
type Optional[T any] struct { // contains filtered or unexported fields }
Optional is a dependency that may be missing.
type PanicError ¶
PanicError is the error filters receive for a recovered panic. The default error writer answers 500 and logs it with the stack.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
type Pipe ¶
type Pipe interface {
Transform(ec ExecutionContext, in any) error
}
Pipe transforms or validates a handler's input after binding. in is a pointer to the bound input struct (the In of Handle), so a pipe can normalise fields in place. An error stops the request; return a *validate.ValidationError or an *errs.HTTPError for a 400. Pipes do not run for HandleRaw routes.
type PipeFunc ¶
type PipeFunc func(ec ExecutionContext, in any) error
PipeFunc is a function used as a Pipe.
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is the resolved guards, interceptors, pipes and filters of one transport handler.
func (*Pipeline) Fail ¶
Fail sends an error that happened before Run, such as a payload that does not decode, through the filters. It returns ErrFiltered or err.
func (*Pipeline) Info ¶
Info describes the handler. Method is the kind ("rpc" or "ws") and Path the pattern.
func (*Pipeline) Run ¶
func (p *Pipeline) Run(ctx context.Context, msg, in any, fn func(ctx context.Context) (any, error)) (out any, err error)
Run handles one message: guards, then interceptors around pipes and fn. in is the decoded input (a pointer, given to pipes and as Data), msg the transport's message or client. An error, or a panic recovered as a *PanicError, goes to the filters; Run returns ErrFiltered when one of them caught it and the error otherwise, for the transport to answer.
type ProvideOption ¶
type ProvideOption func(*Provider)
ProvideOption configures a Provider.
func As ¶
func As[T any]() ProvideOption
As also registers the provider under type T, which is usually an interface the concrete type implements. The same instance is shared across all tokens.
func Durable ¶
func Durable() ProvideOption
Durable makes a RequestScoped provider durable: it is built once per context ID (usually a tenant) chosen by WithContextIDStrategy and reused by every request with that ID, instead of once per request. A durable provider may inject ContextID for the ID and payload, singletons and other durable providers, but not *http.Request or a request scoped provider that is not durable. Its instances get OnApplicationShutdown at shutdown or on App.ReleaseContext; OnRequestEnd is not called.
func For ¶
func For(tok Token) ProvideOption
For registers the provider under tok instead of its own type. Use it with NewToken for named values. Can be given more than once.
func Group ¶
func Group(name string) ProvideOption
Group adds the provider to a named group. Consumers receive every member as a slice through a field tagged group:"name" in an In struct. Groups are application wide. A provider with a group and no For or As option is not registered under its own type.
func WithScope ¶
func WithScope(s Scope) ProvideOption
WithScope sets the provider's scope. The default is Singleton.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider describes how the container gets a value. Create one with Provide, Value or Alias. The zero Provider is invalid.
func Alias ¶
Alias makes alias resolve to the same instance as target. The target's type must be assignable to the alias's type.
func Provide ¶
func Provide(ctor any, opts ...ProvideOption) Provider
Provide registers a constructor function. The container calls it once (or once per consumer for Transient) with its parameters resolved by type.
Accepted shapes:
func(deps...) T func(deps...) (T, error)
A parameter may be any provided type, context.Context (the boot context), *ModuleRef, Optional, Lazy, or a struct embedding In.
By default the provider is registered under TypeOf[T]. For replaces that token, As adds more, and Group alone contributes only to the group.
type ProviderInfo ¶
type ProviderInfo struct {
Module string
Type reflect.Type
Source string
Instance any
Controller bool
}
ProviderInfo describes one built singleton, for discovery.
type RPCHost ¶
type RPCHost interface {
// Pattern is the pattern the handler was registered for.
Pattern() string
// Data is the decoded payload, a pointer to the handler's input.
Data() any
// Message is the transport's own message, for example a *nats.Msg.
Message() any
}
RPCHost gives access to a microservice message.
type RawJSON ¶
type RawJSON []byte
RawJSON as Out is a body already encoded as JSON. It is written as is, with the route's status and a JSON content type, and is not checked, so it must come from json.Marshal or another trusted encoder. The cache interceptor uses it to answer from stored bytes.
type Resolver ¶
Resolver is anything providers can be looked up from: an *App or a *ModuleRef.
type Responder ¶
type Responder interface {
Respond(w http.ResponseWriter, r *http.Request) error
}
Responder is an Out value that writes its own response.
func File ¶
File serves a file from disk with http.ServeContent, which handles Range, If-Modified-Since and content type detection. path must not come from user input without cleaning.
func RedirectTo ¶
RedirectTo redirects to url with code (for example 302 or 307). Return it from a handler for a dynamic target; for a fixed one, use the Redirect route option.
type RouteInfo ¶
type RouteInfo struct {
// Method is the HTTP method, or "" for a route registered with All.
Method string
// Host is the host restriction, if any.
Host string
// Path is the full ServeMux path pattern, including the global prefix
// and URI version.
Path string
// Version is the API version the route serves, if versioning is on.
Version string
// Controller and Module identify where the route was declared.
Controller string
Module string
// Handler is the handler function name.
Handler string
// Input and Output are the In and Out types of a typed handler, nil for
// HandleRaw. Tools such as package openapi read them.
Input, Output reflect.Type
// Status is the success status set with HTTPCode, or 0 for the default.
Status int
// contains filtered or unexported fields
}
RouteInfo describes one registered route.
func RouteOf ¶
RouteOf describes the route handling ctx. The zero RouteInfo is returned outside a request.
func RoutesOf ¶
RoutesOf returns the application's route table from inside a request, for handlers that describe the API, such as package openapi. It returns nil outside a request.
type RouteOption ¶
type RouteOption interface {
// contains filtered or unexported methods
}
RouteOption configures a route. Options given to Router.Use apply first, at controller level, then the route's own options, at route level.
func BodyLimit ¶
func BodyLimit(n int64) RouteOption
BodyLimit overrides the application body size limit for the route. Zero or less means no limit.
func Guards ¶
func Guards(g ...any) RouteOption
Guards adds guards, like @UseGuards. On Router.Use they guard every route of the controller.
func HTTPCode ¶
func HTTPCode(code int) RouteOption
HTTPCode sets the success status code. The default is 201 for POST and 200 otherwise. A handler can still change it per request with SetStatus.
func Interceptors ¶
func Interceptors(i ...any) RouteOption
Interceptors adds interceptors, like @UseInterceptors.
func Options ¶
func Options(opts ...RouteOption) RouteOption
Options combines several route options into one:
func AdminOnly() dygo.RouteOption { return dygo.Options(authMiddleware, dygo.Header("Cache-Control", "no-store")) }
func Redirect ¶
func Redirect(url string, code int) RouteOption
Redirect makes the route redirect after the handler succeeds. Return RedirectTo from the handler for a dynamic target.
func RouteMiddleware ¶
func RouteMiddleware(mw ...Middleware) RouteOption
RouteMiddleware adds net/http middleware to the route. With Router.Use it applies to every route of the controller.
func SetMetadata ¶
func SetMetadata(key, value any) RouteOption
SetMetadata attaches a value to the route, or to every route of the controller when given to Router.Use. Guards and interceptors read it from the ExecutionContext; package meta adds typed keys. Use a key of an unexported type to avoid collisions.
func StrictJSON ¶
func StrictJSON() RouteOption
StrictJSON rejects JSON bodies that contain unknown fields.
func Version ¶
func Version(v ...string) RouteOption
Version sets the route's API versions, overriding Router.Version and the default version. Use VersionNeutral for a route that matches every version.
type Router ¶
type Router interface {
// Prefix adds a path prefix for routes registered through the child.
Prefix(p string) Router
// Host restricts routes to a host, like "api.example.com" or "{tenant}.example.com".
Host(h string) Router
// Version sets the API versions of routes registered through the child.
Version(v ...string) Router
// Use applies options, such as middleware, to every route registered through the child.
Use(opts ...RouteOption) Router
Get(path string, h Handler, opts ...RouteOption)
Post(path string, h Handler, opts ...RouteOption)
Put(path string, h Handler, opts ...RouteOption)
Patch(path string, h Handler, opts ...RouteOption)
Delete(path string, h Handler, opts ...RouteOption)
Head(path string, h Handler, opts ...RouteOption)
Options(path string, h Handler, opts ...RouteOption)
// All matches every method.
All(path string, h Handler, opts ...RouteOption)
}
Router registers routes. Prefix, Host, Version and Use return a child router and leave the receiver unchanged:
func (c *UsersController) Routes(r dygo.Router) {
r = r.Prefix("/users")
r.Get("/", dygo.Handle(c.List))
r.Get("/{id}", dygo.Handle(c.Get))
}
Paths use Go's pattern syntax: {id} for a parameter, {path...} for the rest of the path. GET routes also answer HEAD.
type SSEEvent ¶
SSEEvent is a server-sent event with optional id, event name and retry. Yield it from an SSE sequence to control those fields; any other value is sent as a data-only event.
type Scope ¶
type Scope int
Scope controls how many instances of a provider exist.
const ( // Singleton providers are built once at boot and shared. This is the default. Singleton Scope = iota // Transient providers are built again for every consumer that injects them. // Lifecycle hooks are not called on transient instances. Transient // RequestScoped providers are built at most once per HTTP request. Only // other request scoped providers may inject them directly; singletons and // controllers use [Scoped] instead. Lifecycle hooks are not called on them; // implement [OnRequestEnd] for per-request cleanup. RequestScoped )
type Scoped ¶
type Scoped[T any] struct { // contains filtered or unexported fields }
Scoped gives a singleton (for example a controller) access to a RequestScoped provider. Get returns the instance for the request carried by ctx, building it on first use within that request.
type Controller struct{ tx dygo.Scoped[*sql.Tx] }
func (c *Controller) Create(ctx context.Context, in CreateIn) (*User, error) {
tx, err := c.tx.Get(ctx)
...
}
type Token ¶
type Token interface {
// String describes the token in error messages.
String() string
// Type is the Go type of the value the token resolves to.
Type() reflect.Type
// contains filtered or unexported methods
}
Token identifies a provider in the container. Create one with TypeOf or NewToken.
type Transport ¶
type Transport interface {
// Listen finds its handlers in a (usually with [ProvidersImplementing]),
// builds their pipelines and starts serving. It returns once the
// transport takes messages; a wiring problem is returned as an error.
Listen(ctx context.Context, a *App) error
// Close stops taking messages and waits for running handlers until ctx
// is done.
Close(ctx context.Context) error
}
Transport is a server other than the HTTP one: a message broker consumer, a gRPC server. Packages such as micro/nats implement it.
type VersioningOptions ¶
type VersioningOptions struct {
// contains filtered or unexported fields
}
VersioningOptions configures API versioning. Create one with URIVersioning, HeaderVersioning, MediaTypeVersioning or CustomVersioning and pass it to WithVersioning.
func CustomVersioning ¶
func CustomVersioning(extract func(*http.Request) []string, defaultVersion ...string) VersioningOptions
CustomVersioning reads versions with extract, in order of preference.
func HeaderVersioning ¶
func HeaderVersioning(header string, defaultVersion ...string) VersioningOptions
HeaderVersioning reads the version from a request header, for example "X-API-Version". Requests without the header get the default version.
func MediaTypeVersioning ¶
func MediaTypeVersioning(key string, defaultVersion ...string) VersioningOptions
MediaTypeVersioning reads the version from an Accept header parameter, for example key "v" in "Accept: application/json;v=2".
func URIVersioning ¶
func URIVersioning(defaultVersion ...string) VersioningOptions
URIVersioning puts the version in the path: /v1/users. Routes without a version get defaultVersion.
type WSHost ¶
type WSHost interface {
// Pattern is the event name the handler was registered for.
Pattern() string
// Data is the decoded payload, a pointer to the handler's input.
Data() any
// Client is the connection that sent the message, a *ws.Client.
Client() any
}
WSHost gives access to a WebSocket message.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cache is a key-value cache with a time to live: a Store (in memory by default), typed helpers, an HTTP response cache interceptor and Wrap to cache a function.
|
Package cache is a key-value cache with a time to live: a Store (in memory by default), typed helpers, an HTTP response cache interceptor and Wrap to cache a function. |
|
cmd
|
|
|
dygo
command
Command dygo is the Dygo CLI.
|
Command dygo is the Dygo CLI. |
|
Package config loads typed configuration from the environment and .env files:
|
Package config loads typed configuration from the environment and .env files: |
|
Package console runs one-off commands inside the application context: a seed script, a data fix or a report that needs the same services as the app, without starting the HTTP server.
|
Package console runs one-off commands inside the application context: a seed script, a data fix or a report that needs the same services as the app, without starting the HTTP server. |
|
Package cqrs has command and query buses with typed handlers.
|
Package cqrs has command and query buses with typed handlers. |
|
Package dygotest builds Dygo applications in tests:
|
Package dygotest builds Dygo applications in tests: |
|
Package errs defines HTTP errors for Dygo handlers and the default writer that turns any error into a JSON response:
|
Package errs defines HTTP errors for Dygo handlers and the default writer that turns any error into a JSON response: |
|
Package events is an in-process event emitter.
|
Package events is an in-process event emitter. |
|
examples
|
|
|
orders
command
Command orders is the phase 6 example: one HTTP route creates an order through the command bus, the order service emits an event, a listener puts a confirmation email on the queue, and a scheduled job reports the count.
|
Command orders is the phase 6 example: one HTTP route creates an order through the command bus, the order service emits an event, a listener puts a confirmation email on the queue, and a scheduled job reports the count. |
|
worker
command
Command worker is the phase 1 acceptance app: modules, a dynamic global module, interface binding, a provider group, lifecycle hooks and graceful shutdown, with no HTTP.
|
Command worker is the phase 1 acceptance app: modules, a dynamic global module, interface binding, a provider group, lifecycle hooks and graceful shutdown, with no HTTP. |
|
worker/config
Package config is a dynamic, global module: ForRoot returns a new module configured for the caller.
|
Package config is a dynamic, global module: ForRoot returns a new module configured for the caller. |
|
worker/report
Package report runs a background loop over every reporter in the application, collected through a provider group.
|
Package report runs a background loop over every reporter in the application, collected through a provider group. |
|
worker/store
Package store is a fake database with connection lifecycle hooks.
|
Package store is a fake database with connection lifecycle hooks. |
|
worker/users
Package users shows interface binding with dygo.As and a module that imports another module.
|
Package users shows interface binding with dygo.As and a module that imports another module. |
|
Package health serves liveness and readiness checks:
|
Package health serves liveness and readiness checks: |
|
Package interceptors holds ready made interceptors.
|
Package interceptors holds ready made interceptors. |
|
internal
|
|
|
binder
Package binder fills a handler's input struct from an HTTP request using struct tags.
|
Package binder fills a handler's input struct from an HTTP request using struct tags. |
|
pattern
Package pattern joins and checks route paths written in Go's net/http pattern syntax ("/users/{id}", "/files/{path...}").
|
Package pattern joins and checks route paths written in Go's net/http pattern syntax ("/users/{id}", "/files/{path...}"). |
|
Package logger builds the application's *slog.Logger and provides it through DI:
|
Package logger builds the application's *slog.Logger and provides it through DI: |
|
Package meta adds typed route metadata on top of dygo.SetMetadata:
|
Package meta adds typed route metadata on top of dygo.SetMetadata: |
|
Package micro is the transport-independent part of Dygo microservices: message and event handlers declared by providers, a client to call other services, and an in-process transport for tests.
|
Package micro is the transport-independent part of Dygo microservices: message and event handlers declared by providers, a client to call other services, and an in-process transport for tests. |
|
nats
module
|
|
|
Package middleware holds net/http middleware for production services: request IDs, request logging, CORS, security headers, CSRF protection and compression.
|
Package middleware holds net/http middleware for production services: request IDs, request logging, CORS, security headers, CSRF protection and compression. |
|
Package openapi generates an OpenAPI 3.1 document from the route table.
|
Package openapi generates an OpenAPI 3.1 document from the route table. |
|
Package queue runs background jobs: producers add jobs to a named queue, and processors handle them in workers with retries.
|
Package queue runs background jobs: producers add jobs to a named queue, and processors handle them in workers with retries. |
|
Package ratelimit limits requests per client:
|
Package ratelimit limits requests per client: |
|
Package schedule runs jobs on a cron schedule, at an interval or once after a delay.
|
Package schedule runs jobs on a cron schedule, at an interval or once after a delay. |
|
sql
module
|
|
|
Package validate checks handler input after binding.
|
Package validate checks handler input after binding. |