celerity

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Package celerity is the entry point to the Celerity Go SDK: it holds the application, the registration API, and the handler registry those produce.

Registration is explicit rather than derived from decorators or struct tags, which is what the Node and Python SDKs use and what Go has no equivalent of. Dependencies reach a handler through the closure or the receiver, the way they do in any Go program:

app := celerity.New()
celerity.Get(app, "/orders/{orderId}", orders.Get(store))
celerity.Run(app)

Index

Constants

View Source
const (
	RouteConnect    = "$connect"
	RouteDisconnect = "$disconnect"
	RouteDefault    = "$default"
)

WebSocket routes with a meaning of their own. A message carrying no route, or one nothing else serves, is dispatched to RouteDefault.

View Source
const DefaultWebSocketRouteKey = "event"

DefaultWebSocketRouteKey is the field a WebSocket message carries its route in when the API does not state one.

It matches the runtime's DEFAULT_WEBSOCKET_API_ROUTE_KEY, which is what a blueprint omitting spec.websocketConfig.routeKey resolves to, so a handler registered against a default API declares the tag the runtime builds.

View Source
const Version = "0.1.0"

Version is the SDK version declared to the runtime in the handshake.

Variables

View Source
var (
	Status         = handler.Status
	Statusf        = handler.Statusf
	WrapWithStatus = handler.Wrap

	BadRequest          = handler.BadRequest
	Unauthorized        = handler.Unauthorized
	Forbidden           = handler.Forbidden
	NotFound            = handler.NotFound
	Conflict            = handler.Conflict
	Gone                = handler.Gone
	UnprocessableEntity = handler.UnprocessableEntity
	TooManyRequests     = handler.TooManyRequests
	NotImplemented      = handler.NotImplemented
	ServiceUnavailable  = handler.ServiceUnavailable
)
View Source
var (
	Invalid = handler.Invalid
	Issue   = handler.Issue
)

Re-exported so an application builds issues without a second import.

Functions

func AddGuard

func AddGuard(app *App, name string, g Guard)

AddGuard registers a named guard that handlers refer to through ProtectedBy.

func ConnectionID

func ConnectionID(ctx context.Context) string

ConnectionID returns the WebSocket connection the event arrived on, or the empty string for any other source.

func Consume

func Consume(
	app *App,
	sourceID string,
	h func(context.Context, *handler.ConsumerBatch) (*handler.BatchResult, error),
	opts ...RegisterOption,
)

Consume registers a handler for batches from a queue, stream or event source.

The handler is given the whole batch and returns a handler.BatchResult, so that one failing record redrives that record rather than the batch.

func CustomTag

func CustomTag(handlerName string) string

CustomTag returns the tag identifying a custom handler.

func Delete

func Delete[In, Out any](app *App, route string, h HandlerFunc[In, Out], opts ...RegisterOption)

Delete registers a handler for DELETE requests to route.

func EventFrom

func EventFrom(ctx context.Context) (*handler.Event, bool)

EventFrom returns the event being handled, with its source and identifiers.

func Get

func Get[In, Out any](app *App, route string, h HandlerFunc[In, Out], opts ...RegisterOption)

Get registers a handler for GET requests to route.

func HTTP

func HTTP(
	app *App,
	method, route string,
	h func(context.Context, *handler.Request) (*handler.Response, error),
	opts ...RegisterOption,
)

HTTP registers a handler that works in the wire vocabulary directly, for the cases where decoding into a typed request is not what is wanted.

func HTTPTag

func HTTPTag(method, route string) string

HTTPTag returns the tag identifying an HTTP handler.

Route must already be in the router's form, which NormaliseRoute produces.

func Handler

func Handler[In, Out any](
	app *App,
	name string,
	h HandlerFunc[In, Out],
	opts ...RegisterOption,
)

Handler registers a handler that takes its wiring from the blueprint.

Code states the name and nothing else and the route, method or source is the decided in the blueprint, and the tag is settled against what the blueprint declares before this process declares itself. The blueprint's annotations carry the routing information in this case.

celerity.Handler(app, "getOrderHandler", getOrder)

Everything the blueprint has no concept of is still stated here, so taking wiring from the blueprint costs nothing else:

celerity.Handler(app, "forwardPayload", forward,
    celerity.SkipValidation(),
    celerity.With(ratelimit.Layer(100)),
    celerity.ProtectedBy("jwt"),
    celerity.MaxConcurrent(2),
)

A name the blueprint does not declare fails the handshake naming it, rather than leaving a handler that can never be reached.

Consumer and schedule handlers take a batch and a trigger rather than a decoded input, so they are registered with Consume and Schedule even when their source comes from the blueprint.

func Invoke

func Invoke[In, Out any](
	app *App,
	name string,
	h HandlerFunc[In, Out],
	opts ...RegisterOption,
)

Invoke registers a handler reachable by name, from another handler or from the runtime's local invoke endpoint.

func NormaliseRoute

func NormaliseRoute(route string) string

NormaliseRoute converts a blueprint route template into the router's form, which is what handler tags and handler.Request.Route are built from.

/files/{path+}  ->  /files/{*path}

It is handler.NormaliseRoute: the same translation a serverless adapter applies when it maps a route the platform states in the blueprint's form, so there is one definition of the router's spelling rather than one per mapper.

func OnConnect

func OnConnect[In, Out any](app *App, h HandlerFunc[In, Out], opts ...RegisterOption)

OnConnect registers a handler run when a WebSocket client connects.

func OnDisconnect

func OnDisconnect[In, Out any](app *App, h HandlerFunc[In, Out], opts ...RegisterOption)

OnDisconnect registers a handler run when a WebSocket client disconnects.

func OnMessage

func OnMessage[In, Out any](
	app *App,
	route string,
	h HandlerFunc[In, Out],
	opts ...RegisterOption,
)

OnMessage registers a handler for WebSocket messages arriving on a route.

The route is the value carried by the API's route key, so a message of {"action": "sendMessage", ...} reaches the handler registered for "sendMessage" when the API's route key is "action". Which field that is belongs to the API rather than to this handler: see WithWebSocketRouteKey.

Register RouteDefault to catch messages no other route matches.

func Patch

func Patch[In, Out any](app *App, route string, h HandlerFunc[In, Out], opts ...RegisterOption)

Patch registers a handler for PATCH requests to route.

func Post

func Post[In, Out any](app *App, route string, h HandlerFunc[In, Out], opts ...RegisterOption)

Post registers a handler for POST requests to route.

func Put

func Put[In, Out any](app *App, route string, h HandlerFunc[In, Out], opts ...RegisterOption)

Put registers a handler for PUT requests to route.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the identifier the runtime gave this delivery, whatever the source, which is what ties a handler's own logging to the runtime's.

func Run

func Run(app *App)

Run starts the application, choosing how from the environment, and blocks until it is shut down.

The mode is taken from the environment rather than chosen by the developer so that one binary serves every deployment target: the same artefact runs under the runtime, under Lambda, and under the CLI's extraction step.

func RunContext

func RunContext(ctx context.Context, app *App) error

RunContext is Run with a caller-supplied context, for tests and for applications that own their own shutdown signalling.

func Schedule

func Schedule(
	app *App,
	sourceID string,
	h func(context.Context, *handler.ScheduleTrigger) error,
	opts ...RegisterOption,
)

Schedule registers a handler run when a scheduled rule fires.

func SourceTag

func SourceTag(sourceID, handlerName string) string

SourceTag returns the tag identifying a consumer or schedule handler, keyed by the source it is bound to.

func WebSocketEventFrom

func WebSocketEventFrom(ctx context.Context) (*handler.WebSocketMessage, bool)

WebSocketEventFrom returns the WebSocket message being handled.

It is how a handler reaches the connection it should answer on where a WebSocket message is acknowledged rather than replied to, so anything the client sees is pushed back through WebSocketSenderFrom to a connection named here.

func WebSocketSenderFrom

func WebSocketSenderFrom(ctx context.Context) (handler.WebSocketSender, bool)

WebSocketSenderFrom returns the sender a handler pushes to connected clients through.

It is the runtime's IPC side channel under the Celerity runtime and the provider's push API under a serverless adapter, and a handler does not need to know which. The second return value is false when the event's source has no sender, which is every source other than WebSocket.

func WebSocketTag

func WebSocketTag(routeKey, route string) string

WebSocketTag returns the tag identifying a WebSocket message handler.

The route key names the field carrying the route, and the route is its value, so {"action": "sendMessage"} on an API keyed by "action" is action::sendMessage. Both halves come from the API rather than the handler, which is why registration takes only the route.

func WithEvent

func WithEvent(ctx context.Context, ev *handler.Event) context.Context

WithEvent returns a context carrying the event being handled.

The pipeline does this for every dispatch; a handler does not need to.

Types

type App

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

App is one Celerity application: everything it serves, and how it should serve it.

func New

func New(opts ...Option) *App

New creates an application.

func (*App) Adapter

func (a *App) Adapter() serverless.Adapter

Adapter returns an adapter given explicitly, or nil when the linked one is to be used.

func (*App) Config

func (a *App) Config() *config.Service

Config returns the application's configuration.

This is never nil, an application whose deployment described no store gets a service with no namespaces, so a provider reading it is told the application declares no celerity/config resource rather than having to guard against nothing.

Read by resource providers resolving a blueprint name to the identifier a deployment recorded. A handler is given the service as an argument instead.

func (*App) Err

func (a *App) Err() error

Err returns every error collected during registration, joined.

func (*App) Extracting

func (a *App) Extracting() bool

Extracting reports that this process is describing itself for the CLI rather than serving, satisfying resources.Host.

func (*App) HandlerLimits

func (a *App) HandlerLimits() map[string]int

HandlerLimits returns the per-handler concurrency caps, keyed by tag.

Read after App.ReconcileTags rather than before as reconciliation can change a WebSocket handler's tag to the one the blueprint declares, and a cap keyed by the tag built before that would name a handler the runtime does not know.

func (*App) Logger

func (a *App) Logger() telemetry.Logger

Logger returns the application's logger.

func (*App) Manifest

func (a *App) Manifest() *manifest.Manifest

Manifest builds the handler manifest for this application.

Resource references are left to the extraction tool's static pass: a reference captured in a closure is not reachable from a running program, so filling the annotation in here would report only what happened to be visible and would look complete while being wrong.

func (*App) Pipeline

func (a *App) Pipeline(reg *Registration) layer.Next

Pipeline builds the full entry point for a registration: guards, then layers outermost to innermost, then the handler.

It is exported so that a serverless adapter maintained outside this repository composes the same pipeline the in-repo ones do, rather than approximating it.

func (*App) ReconcileTags

func (a *App) ReconcileTags(blueprint []BlueprintHandler) ([]string, error)

ReconcileTags settles every tag that code could not state on its own, and returns the tags this process serves.

Two kinds of handler need it, for opposite reasons.

A handler registered with Handler states only its name where its route, method or source is the blueprint's to decide, so its tag is whatever the blueprint gives the handler of that name. One that the blueprint does not declare is an error rather than a handler with no tag, since it could never be dispatched to.

A WebSocket handler registered with OnMessage states its route, matching the other language SDKs where a decorator carries it and extraction writes it into the blueprint. The route key belongs to the API, so it is taken from whatever the blueprint keys that route by. One left unmatched keeps the tag built from WithWebSocketRouteKey, so the handshake reports the tag the handler actually declared rather than hiding a mismatch behind a guess.

The runtime sends what the blueprint declares before this process declares itself, which is the only window in which both are known.

func (*App) RecordResourceRef

func (a *App) RecordResourceRef(kind resources.Kind, name string)

RecordResourceRef notes that the application reaches a resource, satisfying resources.Host.

This is the application-wide set. Per-handler attribution comes from the static extraction pass, which is the only thing that can see through a closure; this set is what that pass is cross-checked against, so a reference the run saw and the analysis missed is reported rather than silently dropped.

func (*App) Registry

func (a *App) Registry() *Registry

Registry returns the handlers registered so far.

Serverless adapters and the manifest emitter read the application through it.

func (*App) Resolver

func (a *App) Resolver() serverless.Resolver

Resolver adapts the registry to what a serverless adapter resolves through.

Run uses it, and an application that owns its own entry point rather than handing control to Run passes it to serverless.Serve or serverless.NewInvoker itself.

func (*App) ResourceError

func (a *App) ResourceError(err error)

ResourceError collects a resource that could not be built, satisfying resources.Host.

func (*App) ResourceProvider

func (a *App) ResourceProvider() resources.Provider

ResourceProvider returns a provider given explicitly, satisfying resources.Host. Nil means the linked provider is used.

func (*App) ResourceRefs

func (a *App) ResourceRefs() map[resources.Kind][]string

ResourceRefs returns every resource the application reaches, by kind, sorted.

type BlueprintHandler

type BlueprintHandler struct {
	// Name is the blueprint resource name.
	Name string
	// PublishedName is the name the blueprint publishes the handler under, from
	// spec.handlerName. Empty where it sets none.
	PublishedName string
	// Tag is what the runtime addresses the handler by.
	Tag string
}

BlueprintHandler is one handler the blueprint declares, as the runtime reports it before the handshake.

It is this package's own type rather than the protocol's, so reconciliation can be described and tested without the wire contract.

type Guard

type Guard = guard.Guard

Guard decides whether an event may reach its handler.

type HandlerFunc

type HandlerFunc[In, Out any] func(ctx context.Context, in In) (Out, error)

HandlerFunc is a typed handler: the framework decodes the request into In and encodes Out into the response, so a handler deals only in its own types.

type Mode

type Mode string

Mode is how an application was asked to run.

const (
	// ModeRuntime serves the Celerity runtime over the IPC stream.
	ModeRuntime Mode = "runtime"
	// ModeServerless hands off to a provider's serverless event loop.
	ModeServerless Mode = "serverless"
	// ModeExtract prints the handler manifest and exits.
	ModeExtract Mode = "extract"
)

func DetectMode

func DetectMode() Mode

DetectMode reports how the process was asked to run.

The runtime's own variables are checked first: a handler process started by the runtime is told so explicitly, whereas a serverless platform is inferred.

type Option

type Option func(*options)

Option configures an application.

func WithAdapter

func WithAdapter(a serverless.Adapter) Option

WithAdapter supplies the serverless adapter explicitly.

Applications do not normally need it: importing an adapter registers it, and the one whose platform the process is running on is selected at startup. It is here for a test that wants to drive a fake adapter.

func WithConfig

func WithConfig(svc *config.Service) Option

WithConfig supplies the configuration service explicitly.

Applications do not normally need it as the stores a blueprint's celerity/config resources were deployed as are described in the environment, and reading them needs whichever provider module is linked, so config.FromEnvironment does this at startup. It is here for a test and for a local run that wants to read from a map rather than reach a real store:

cfg := config.New()
cfg.Register("settings", config.NewNamespace(
	config.MapBackend{"settings": {"REGION": "eu-west-2"}}, "settings"))

app := celerity.New(celerity.WithConfig(cfg))

The service is not put in a handler's context. It exists before any event does and is the same object for every one of them, so a handler that reads configuration is given it, the way it is given a store or a client:

celerity.Get(app, "/orders", orders.List(store, cfg))

func WithLayers

func WithLayers(layers ...layer.Layer) Option

WithLayers adds application-scoped layers, which wrap every handler.

func WithLogger

func WithLogger(l telemetry.Logger) Option

WithLogger replaces the logger handlers receive through telemetry.LoggerFrom.

Without one an application writes through telemetry.NewLogger: JSON where a log aggregator is reading, human-readable in a development session.

func WithResourceProvider

func WithResourceProvider(p resources.Provider) Option

WithResourceProvider supplies the platform implementation behind the resource interfaces explicitly.

Applications do not normally need it: importing a provider registers it, and the linked one is used. It is here for a test that wants to supply a fake.

func WithValidator

func WithValidator(v Validator) Option

WithValidator sets the validator run over every decoded input, after the input's own Validate where it has one.

func WithWebSocketRouteKey

func WithWebSocketRouteKey(key string) Option

WithWebSocketRouteKey sets the field in a WebSocket message that carries the route, which is a property of the API rather than of any handler:

{"action": "sendMessage", "data": {...}}

Under the Celerity runtime this is a fallback. The blueprint states it, and the runtime sends every declared handler tag before the handshake, so the tags are reconciled against what the blueprint actually says. It matters where there is no runtime to ask, which is every serverless deployment.

type RegisterOption

type RegisterOption func(*registerOptions)

RegisterOption configures one registration.

func MaxConcurrent

func MaxConcurrent(n int) RegisterOption

MaxConcurrent caps how many events of this handler may be in flight at once, so a slow or expensive handler cannot consume the whole concurrency window and starve the others.

Unlike the size of that window, which depends on the host and is set by whoever deploys, this says which handler is expensive relative to the rest, and that is known here rather than at deployment. It is a cap, not a reservation, it never guarantees the handler that much.

func Named

func Named(name string) RegisterOption

Named sets the blueprint resource name for a handler.

Without it the name is derived from the handler function's own name, which is what a blueprint generated from the manifest will use.

func ProtectedBy

func ProtectedBy(guards ...string) RegisterOption

ProtectedBy names the guards that must allow an event before it reaches this handler.

func Public

func Public() RegisterOption

Public marks a handler as reachable without authorisation, overriding an application-wide guard.

func PublishedAs

func PublishedAs(name string) RegisterOption

PublishedAs sets the name a deployment addresses this handler by, which is the blueprint's spec.handlerName.

func SkipValidation

func SkipValidation() RegisterOption

SkipValidation turns validation off for one handler, including the input's own Validate where it has one.

For a handler that takes what it is given, such as one accepting a payload it forwards without reading, or one whose checks depend on state the type cannot see.

func Uses

func Uses(resourceNames ...string) RegisterOption

Uses declares blueprint resources this handler reaches, so the deployment grants it access to them.

Extraction finds resources through the reference graph on its own, and Uses is additive to that. It is for what analysis cannot see: a resource named by a value that is not a compile-time constant, or one reached by a mechanism the reference graph does not model.

There is no option to narrow. Removing a reference the walk found would let a handler call something it has no permission for, failing at runtime rather than at build time.

func ValidateWith

func ValidateWith(v Validator) RegisterOption

ValidateWith uses a different validator for one handler, in place of the application's.

For a handler whose rules are not the rest of the application's, for example, a public endpoint that is stricter about what it accepts, or one migrating to new rules while the others keep the old.

func With

func With(layers ...layer.Layer) RegisterOption

With adds handler-scoped layers, applied inside any application layers.

type Registration

type Registration struct {
	// Tag addresses this handler in the runtime, built by [HTTPTag] and friends.
	Tag string
	// Name is the blueprint resource name this handler is declared as.
	Name string
	// PublishedName is the name the blueprint publishes the handler under, from
	// spec.handlerName. Empty when the blueprint does not set one.
	PublishedName string
	Kind          handler.Kind

	// Method and Route are set for HTTP handlers, Route in the router's form.
	Method string
	Route  string
	// RouteKey is set for WebSocket handlers.
	RouteKey string
	// SourceID is set for consumer and schedule handlers.
	SourceID string

	// SourceFile and SourceLine are the location of the registration call,
	// captured for error messages and for the handler manifest.
	SourceFile string
	SourceLine int
	// FuncPath is the fully qualified name of the handler function, which roots
	// the extraction tool's static resource walk.
	FuncPath string

	Handler handler.Func
	Layers  []layer.Layer
	Guards  []string
	// Uses names resources declared explicitly through [Uses], additive to what
	// static extraction finds.
	Uses []string
	// Public marks a handler reachable without authorisation.
	Public bool
	// MaxConcurrent caps how many events of this handler may be in flight,
	// zero meaning uncapped.
	MaxConcurrent int
	// FromBlueprint marks a handler that stated only its name, leaving its
	// route, method or source to the blueprint. Its tag is settled by
	// [App.ReconcileTags] rather than at registration.
	FromBlueprint bool
}

Registration is one handler as it was registered, along with everything the framework needs in order to run it and everything the CLI needs in order to describe it.

type Registry

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

Registry holds every handler an application has registered, keyed by tag.

It is the single source of truth for three things that must agree: what the dispatcher routes against, what the handshake declares to the runtime, and what the handler manifest reports to the CLI.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry.

func (*Registry) Add

func (r *Registry) Add(reg *Registration) error

Add registers a handler.

Registering two handlers under one tag is a programming error rather than a last-write-wins: the runtime addresses handlers by tag, so the second would silently take every event meant for the first.

func (*Registry) All

func (r *Registry) All() []*Registration

All returns every registration in registration order.

func (*Registry) ByName

func (r *Registry) ByName(name string) (*Registration, bool)

ByName returns the handler whose blueprint resource name or published name is name. Serverless adapters resolve CELERITY_HANDLER_ID through this.

func (*Registry) Get

func (r *Registry) Get(tag string) (*Registration, bool)

Get returns the handler registered under tag.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered handlers.

func (*Registry) OfKind

func (r *Registry) OfKind(kind handler.Kind) []*Registration

OfKind returns every registration for one event source.

func (*Registry) Retag

func (r *Registry) Retag(from, to string)

Retag moves a registration to a different tag, keeping registration order.

Used when the blueprint declares a WebSocket route key other than the one a tag was built with, which is not known until the runtime sends its configuration.

func (*Registry) Tags

func (r *Registry) Tags() []string

Tags returns every registered tag, sorted, which is what the IPC handshake declares to the runtime.

type SelfValidating

type SelfValidating interface {
	Validate() error
}

SelfValidating is implemented by a request type that checks itself.

Returning a handler.ValidationError answers with structured detail; returning anything else is answered as a 400 with no detail, since a message this SDK did not compose may describe the implementation.

func (r CreateOrder) Validate() error {
    if r.Total <= 0 {
        return celerity.Invalid("the order is not valid",
            celerity.Issue("too_small", "must be greater than zero", "total"))
    }
    return nil
}

type StatusCoder

type StatusCoder = handler.StatusCoder

Errors carrying a status, re-exported so an application deals with one package. A handler returning one of these is answered with that status; any other error is a fault, reported to the runtime and answered 500.

func getOrder(ctx context.Context, in GetOrder) (Order, error) {
    order, err := store.Get(ctx, in.OrderID)
    if errors.Is(err, sql.ErrNoRows) {
        return Order{}, celerity.NotFound("no such order")
    }
    return order, err
}

type StatusError

type StatusError = handler.StatusError

Errors carrying a status, re-exported so an application deals with one package. A handler returning one of these is answered with that status; any other error is a fault, reported to the runtime and answered 500.

func getOrder(ctx context.Context, in GetOrder) (Order, error) {
    order, err := store.Get(ctx, in.OrderID)
    if errors.Is(err, sql.ErrNoRows) {
        return Order{}, celerity.NotFound("no such order")
    }
    return order, err
}

type UnresolvedHandlerError

type UnresolvedHandlerError struct {
	// Names are registered in code and absent from the blueprint.
	Names []string
	// Declared are the names the blueprint does declare, which is what a typo
	// is most easily spotted against.
	Declared []string
}

UnresolvedHandlerError reports handlers that take their wiring from the blueprint and were not found in it.

func (*UnresolvedHandlerError) Error

func (e *UnresolvedHandlerError) Error() string

type ValidationError

type ValidationError = handler.ValidationError

type ValidationIssue

type ValidationIssue = handler.ValidationIssue

type Validator

type Validator interface {
	Validate(input any) error
}

Validator checks a decoded input.

It is what a struct-tag validation library is adapted through. The adapter should return a handler.ValidationError, or any error implementing handler.IssueReporter, so the caller is answered with issues rather than with a bare message.

Jump to

Keyboard shortcuts

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