SDK Contract
github.com/TDB-Group/anvil/sdk is the application contract.
Application code imports it for marker types, route table types, request context, middleware interfaces, error contracts, provider contracts, and protocol-neutral stream/socket contracts.
The SDK is the package application code imports for Anvil concepts that appear in route files and handlers. Response bodies, response status codes, framework configuration, cloud clients, logging, and application helpers stay in your application or in the selected driver.
Marker Types
Section titled “Marker Types”Markers are empty structs. The compiler detects them through Go type identity.
type Projects struct { sdk.Controller `path:"/projects"`}
type Services struct { sdk.Bundle}
type ReindexProjects struct { sdk.QueueJob `queue:"projects.reindex"`}Markers:
| Type | Purpose |
|---|---|
Controller | Declares an HTTP Routes table and handler methods. |
Group | Creates a nested route tree with shared path and middleware. |
Bundle | Marks a dependency group whose inject-tagged fields are wired. |
HttpEndpoint | Low-level HTTP endpoint marker for scanner extensions. |
GrpcEndpoint | gRPC endpoint marker. |
GraphQLEndpoint | GraphQL endpoint marker. |
GraphQLEndpointWith[Policy] | GraphQL endpoint marker with an endpoint policy bundle. |
QueueJob | Queue/background job marker. |
The HttpEndpoint and GrpcEndpoint names intentionally use Go-style mixed
case preferred by the project, not all-caps acronym naming.
HTTP Route Markers
Section titled “HTTP Route Markers”HTTP controllers declare routes in a nested Routes table:
Routes struct { List sdk.GET `path:"/"` Create sdk.POST `path:"/"` Get sdk.GET `path:"/:projectId"` Update sdk.PUT `path:"/:projectId"` Patch sdk.PATCH `path:"/:projectId"` Delete sdk.DELETE `path:"/:projectId"` Live sdk.WS `path:"/:projectId/live"`}Policy variants attach a policy bundle by type:
Routes struct { Create sdk.POSTWith[WritePolicy] `path:"/"` Live sdk.WSWith[LivePolicy] `path:"/:projectId/live"`}Use[T] marks a policy field as middleware:
type WritePolicy struct { _ sdk.Use[RequireActor] _ sdk.Use[AuditTrail]}Handler Signatures
Section titled “Handler Signatures”HTTP handlers use:
func (c *Controller) List(ctx sdk.Ctx) ([]ProjectDTO, error)func (c *Controller) Get(ctx sdk.Ctx, req GetProjectRequest) (ProjectDTO, error)WebSocket handlers use:
func (c *Controller) Live(ctx sdk.Ctx, socket sdk.WebSocket) errorRequest structs are optional. When present, generated code binds fields from
param, query, header, and local tags. Exported non-anonymous fields
without those binding tags are treated as body fields and decoded through the
driver’s codec registry before validation runs.
sdk.Ctx
Section titled “sdk.Ctx”type Ctx interface { Context() context.Context Native() any Request() HTTPRequest Response() HTTPResponse Locals() LocalStore Errors() ErrorFactory Next() (any, error)}Methods:
Context()returns the request context for cancellation, deadlines, tracing, and service calls.Native()returns the driver-owned native request context. Use it only when you intentionally depend on the selected driver.Request()exposes framework-neutral request data.Response()stores response metadata selected by handler code.Locals()stores request-scoped middleware values.Errors()creates Anvil failures with generated route context attached.Next()continues the generated middleware chain.
Next() lives on the context so middleware can call ctx.Next() without a
separate function parameter. Normal HTTP handlers return (body, error).
Middleware calls Next when it wants the generated chain to continue.
Request And Response Contracts
Section titled “Request And Response Contracts”type HTTPRequest interface { Method() string Path() string Param(name string) string Query(name string) string Header(name string) string Body() []byte Decode(out any) error}Generated code handles binding before the handler runs. Use
Request() directly for low-level cases such as streaming negotiation,
signature verification, raw body inspection, or custom decoding.
type HTTPResponse interface { Status(code int) Header(name string, value string) Stream(handler func(HTTPStream) error) error}Handlers return the body as their ordinary return value. Set status, headers, or a
stream on ctx.Response() when needed.
Streaming
Section titled “Streaming”type HTTPStream interface { Context() context.Context Write(data []byte) error Flush() error}Use ctx.Response().Stream for long-lived HTTP responses such as server-sent
events, NDJSON, chunked JSON, and file downloads.
WebSockets
Section titled “WebSockets”type WebSocket interface { Context() context.Context Native() any Subprotocol() string Read() (WebSocketMessage, error) Write(message WebSocketMessage) error Close(code WebSocketCloseCode, reason string) error}Native() follows the same escape-hatch rule as sdk.Ctx.Native(). Use it
when direct driver access is worth losing portability.
Message types:
sdk.WebSocketTextsdk.WebSocketBinarysdk.WebSocketClosesdk.WebSocketPingsdk.WebSocketPongClose codes:
sdk.WebSocketCloseNormalsdk.WebSocketCloseGoingAwaysdk.WebSocketCloseProtocolErrorsdk.WebSocketCloseUnsupportedDatasdk.WebSocketClosePolicyViolationsdk.WebSocketCloseMessageTooBigsdk.WebSocketCloseInternalErrorMiddleware Contracts
Section titled “Middleware Contracts”Middleware is attached with sdk.Use[T] on a group or policy type. The
compiler checks the method signature on T and emits the matching protocol
chain. See Callbacks and Middleware
for the difference between startup hooks and request-time middleware, then read
Middleware for placement rules and
execution order.
The method name is the protocol contract. A type with HandleGRPC is gRPC
middleware. A type with BeforeHTTP is HTTP middleware. If one type implements
both, Anvil can attach it to both protocol-specific chains, but one middleware
type per protocol keeps application route groups easier to read.
HandleHTTP, HandleGraphQL, HandleGRPC, and HandleQueue are middleware
methods that own the continuation. They decide whether the generated chain
continues by calling ctx.Next(). HTTP also exposes BeforeHTTP,
OnHTTPError, and AfterHTTP because many HTTP concerns only need one phase
of the request. These are request-time middleware methods, not application
lifecycle hooks.
HTTP middleware can implement one or more interfaces:
type HTTPMiddleware interface { HandleHTTP(ctx Ctx) (any, error)}
type HTTPBeforeMiddleware interface { BeforeHTTP(ctx Ctx) error}
type HTTPAfterMiddleware interface { AfterHTTP(ctx Ctx, body any, err error) (any, error)}
type HTTPErrorMiddleware interface { OnHTTPError(ctx Ctx, err error) error}Behavior:
HandleHTTPcontrols the chain manually and callsctx.Next()to continue.BeforeHTTPruns before the next middleware or handler.AfterHTTPruns after the next middleware or handler and can replace the body or error.OnHTTPErrorruns only when the next middleware or handler returns an error. Returningnilmarks the error as handled.
If a value implements several HTTP middleware interfaces, the driver runs them
as one value: BeforeHTTP, then HandleHTTP or automatic continuation, then
OnHTTPError if needed, then AfterHTTP.
GraphQL, gRPC, and queue middleware use one continuation method each:
type GraphQLMiddleware interface { HandleGraphQL(ctx GraphQLCtx) (GraphQLResponse, error)}
type GRPCMiddleware interface { HandleGRPC(ctx GRPCCtx) (any, error)}
type QueueMiddleware interface { HandleQueue(ctx QueueCtx) error}Prefer one middleware type per protocol in application code. A single type can implement several protocol methods when a shared plugin deliberately needs that shape, but protocol-specific types keep route groups readable.
Providers
Section titled “Providers”type Provider interface { Key() string Build(resolver DependencyResolver) (any, error)}
type DependencyResolver interface { Resolve(key string) (any, error)}Providers are built while the app is wired, before transports accept requests. Return errors from providers instead of panicking. Use explicit provider keys when two dependencies have the same Go type but different meanings.
Plugins And Lifecycle
Section titled “Plugins And Lifecycle”type Plugin interface { Name() string Register(lifecycle AppLifecycle) error}AppLifecycle lets plugins register boot hooks, shutdown hooks, error
observers, error mappers, providers, and event bus subscribers. Read
Callbacks and Middleware
for the runtime order.