Documentation
¶
Overview ¶
Package arc is the composition entry point for Cratis Arc for Go.
NewBuilder composes explicit typed command/query registrations, authentication, identity and discovery. Build is single-attempt and activates no dependencies; Start activates explicit lifecycle hooks. Application implements http.Handler for embedding, or owns a listener through Serve and Run. Shutdown closes admission, drains work and stops hooks in reverse order. Callbacks must honor context.
Builders are single-owner construction state. Built applications support concurrent requests when borrowed collaborators do. Ordinary middleware and providers are trusted code, not a sandbox. No framework container is required.
Importing Arc starts no work or I/O. The hosted HTTP surface serves commands, snapshot queries and observable queries: direct Server-Sent Events and WebSocket streams plus the multiplexed SSE and WebSocket query hubs. Snapshot HTTP CQRS does not require event sourcing.
Code generation, Chronicle and MongoDB live in separate nested modules that are not part of this module and are not yet released: github.com/cratis/arc.go/tools provides arc-gen, which emits registration adapters, TypeScript proxies, OpenAPI documents and Screenplay output, and github.com/cratis/arc.go/integrations/chronicle and github.com/cratis/arc.go/integrations/mongodb provide the optional adapters.
Start with the documentation, read observable queries and code generation for streaming and generated clients, and use the parity ledger for the exact, bounded contracts and the differences from C# Arc.
Example ¶
package main
import (
"fmt"
"github.com/cratis/arc.go/commands"
"github.com/cratis/arc.go/concepts"
"github.com/cratis/arc.go/metadata"
)
func main() {
command := metadata.Command{Type: metadata.TypeName{Namespace: "Tasks.Registration", Name: "RegisterTask"}}
catalog := metadata.Catalog{Version: metadata.Version, Commands: []metadata.Command{command}}
routes, err := metadata.Resolve(catalog, metadata.DefaultOptions())
if err != nil {
fmt.Println(err)
return
}
id, err := concepts.ParseUUID("00112233-4455-4677-8899-aabbccddeeff")
if err != nil {
fmt.Println(err)
return
}
// This constructs an outcome; it does not invoke a command or host an endpoint.
result := commands.WithResponse(id, struct {
ID string `json:"id"`
}{ID: "a1"})
response, present := result.Response()
fmt.Println(routes[0].Method, routes[0].Path)
fmt.Println(result.StatusCode(), response.ID, present)
}
Output: POST /api/tasks/registration/register-task 200 a1 true
Index ¶
- Constants
- Variables
- func NewActionHandler[T any](action func(context.Context, T) (ActionResult, error), ...) (http.Handler, error)
- func RegisterIdentityDetails[T any](b *Builder, name string, provider identity.DetailsProvider[T]) error
- func RegisterSchema[T any](b *Builder, document json.RawMessage) error
- func RegisterScopedIdentityDetails[T any](b *Builder, name string, factory Factory[identity.DetailsProvider[T]], ...) error
- type ActionOptions
- type ActionResult
- type Application
- func (a *Application) Catalog() metadata.Catalog
- func (a *Application) Commands() commands.Pipeline
- func (a *Application) Endpoints() []metadata.Endpoint
- func (a *Application) Queries() queries.Pipeline
- func (a *Application) Run(ctx context.Context, address string) error
- func (a *Application) Serve(ctx context.Context, listener net.Listener) error
- func (a *Application) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *Application) Shutdown(ctx context.Context) error
- func (a *Application) Start(ctx context.Context) error
- type Builder
- func (b *Builder) AddLifecycle(name string, participant Lifecycle) error
- func (b *Builder) AddTenantsProvider(name string, factory Factory[TenantsProvider], keys ...di.Key) error
- func (b *Builder) AddUsersProvider(name string, factory Factory[UsersProvider], keys ...di.Key) error
- func (b *Builder) Build() (*Application, error)
- func (b *Builder) Catalog() metadata.Catalog
- func (b *Builder) Commands() *commands.Registry
- func (b *Builder) ExpectGeneratedEndpoints(profile string, endpoints []metadata.Endpoint) error
- func (b *Builder) Handle(pattern string, handler http.Handler) error
- func (b *Builder) Policies() *authorization.Registry
- func (b *Builder) Queries() *queries.Registry
- func (b *Builder) RegisterCommand(r commands.Registration) error
- func (b *Builder) RegisterQuery(r queries.Registration) error
- func (b *Builder) RegisterReadModel(r queries.ModelRegistration) error
- func (b *Builder) Use(middleware ...Middleware) error
- func (b *Builder) Validators() *validation.Registry
- type ConfigurationError
- type Factory
- type HTTPOptions
- type IdentityOptions
- type IntrospectionOptions
- type Lifecycle
- type Middleware
- type ObservableOptions
- type Options
- type QueryHealth
- type QueryHealthHub
- type QueryHealthOptions
- type TenantsProvider
- type UsersProvider
Examples ¶
Constants ¶
const QueryHealthName queries.FullyQualifiedQueryName = "QueryHealth.ObserveHealth"
QueryHealthName is the protected framework query's backend and hub identity.
Variables ¶
var ( // ErrInvalidOptions identifies invalid construction or registration options. ErrInvalidOptions = errors.New("invalid Arc options") // ErrFrozen identifies mutation after the root's single Build attempt. ErrFrozen = errors.New("arc builder frozen") // ErrNotStarted identifies admission before Start completes. ErrNotStarted = errors.New("arc application not started") // ErrShuttingDown identifies admission after shutdown begins. ErrShuttingDown = errors.New("arc application shutting down") // ErrStopped identifies an application which cannot restart. ErrStopped = errors.New("arc application stopped") // ErrAlreadyServing identifies a second owned server. ErrAlreadyServing = errors.New("arc application already serving") // ErrRouteConflict identifies overlapping HTTP ownership. ErrRouteConflict = errors.New("arc route conflict") // ErrAuthenticationSetup identifies discovery requiring unavailable authentication. ErrAuthenticationSetup = errors.New("arc discovery authentication unavailable") ErrSchemaUnavailable = errors.New("arc schema unavailable") )
Functions ¶
func NewActionHandler ¶
func NewActionHandler[T any](action func(context.Context, T) (ActionResult, error), options ActionOptions[T]) (http.Handler, error)
NewActionHandler adapts an explicit traditional POST action to Arc envelopes. Mount it with Builder.Handle for both the action and its /validate route; no routes are registered automatically. A case-insensitive /validate suffix runs binding and validation but NEVER the action or a Raw handler. Other methods receive 405. T must be a struct without methods; invalid configuration fails construction without invoking callbacks. An absent body binds a zero T; a present body must be one JSON object. Unknown properties are ignored and duplicate declared wire names rejected, as in the existing serializer.
The adapter consumes hosting correlation/identity/tenant context, but does not invoke catalog authorization policies. Protect it with application middleware, like any Builder.Handle route. Used outside Arc, correlation is zero unless supplied through correlation.WithID. Errors/panics before raw publication use production redaction regardless of the host's development settings. Raw handlers are an explicit unwrapped net/http boundary, not buffered or recovered here after they may have committed headers.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
"net/http/httptest"
"strings"
arc "github.com/cratis/arc.go"
"github.com/cratis/arc.go/validation"
)
type actionEnvelope struct {
CorrelationID string `json:"correlationId"`
IsSuccess bool `json:"isSuccess"`
IsAuthorized bool `json:"isAuthorized"`
IsValid bool `json:"isValid"`
HasExceptions bool `json:"hasExceptions"`
ValidationResults []validation.Result `json:"validationResults"`
ExceptionMessages []string `json:"exceptionMessages"`
Response json.RawMessage `json:"response"`
}
func main() {
type input struct{ Name string }
handler, err := arc.NewActionHandler(func(_ context.Context, value input) (arc.ActionResult, error) {
return arc.ActionResult{Response: "Hello, " + value.Name}, nil
}, arc.ActionOptions[input]{})
if err != nil {
fmt.Println(err)
return
}
builder, err := arc.NewBuilder(arc.Options{})
if err != nil {
fmt.Println(err)
return
}
for _, route := range []string{"POST /greet", "POST /greet/validate"} {
if err := builder.Handle(route, handler); err != nil {
fmt.Println(err)
return
}
}
app, err := builder.Build()
if err != nil {
fmt.Println(err)
return
}
if err := app.Start(context.Background()); err != nil {
fmt.Println(err)
return
}
response := httptest.NewRecorder()
app.ServeHTTP(response, httptest.NewRequest("POST", "/greet", strings.NewReader(`{"name":"Ada"}`)))
var result actionEnvelope
if err := json.Unmarshal(response.Body.Bytes(), &result); err != nil {
fmt.Println(err)
return
}
fmt.Println(response.Code, string(result.Response))
if err := app.Shutdown(context.Background()); err != nil {
fmt.Println(err)
}
}
Output: 200 "Hello, Ada"
func RegisterIdentityDetails ¶
func RegisterIdentityDetails[T any](b *Builder, name string, provider identity.DetailsProvider[T]) error
RegisterIdentityDetails registers a borrowed, concurrently callable provider.
func RegisterSchema ¶
func RegisterSchema[T any](b *Builder, document json.RawMessage) error
RegisterSchema installs a copied JSON schema override for an exact Go type. Overrides must be schema objects or booleans. They do not run application codecs.
func RegisterScopedIdentityDetails ¶
func RegisterScopedIdentityDetails[T any](b *Builder, name string, factory Factory[identity.DetailsProvider[T]], dependencies ...di.Key) error
RegisterScopedIdentityDetails registers a lazy request-scoped details factory. Declared dependency keys are checked at Build without resolving them.
Types ¶
type ActionOptions ¶
type ActionOptions[T any] struct { // FromRequest optionally binds query/route values into a fresh T. The caller // chooses their precedence and returns malformed input as *commands.DecodeError; // other errors are redacted. It must not read Body or perform business effects. // Nondefault request values fill default body fields, including body 0/false. // Explicit body empty strings and nonnil scalar pointers win. This bounded // profile admits flat built-in scalars and scalar pointers, not custom codecs, // collections or embedded fields. Wire names match exactly, as in Arc's JSON // serializer. Null plain strings are treated as missing during merging; use // *string to retain null separately from empty. Without FromRequest, only the // JSON body is bound using the serializer's normal null rules. FromRequest func(*http.Request) (T, error) // Validate optionally returns safe input findings before any action. It also // runs on /validate requests. Nil means no application validation. Validate func(context.Context, T) ([]validation.Result, error) // TreatWarningsAsErrors retains Warning as well as Error findings. By default // only Error findings block; X-Ignore-Warnings: true overrides this option. TreatWarningsAsErrors bool // MaxBodyBytes defaults to 1 MiB. Negative limits are invalid. MaxBodyBytes int64 // MaxQueryBytes defaults to 8 KiB, measured before invoking FromRequest. MaxQueryBytes int // MaxResponseBytes defaults to 16 MiB for envelopes. Raw owns its own limits. MaxResponseBytes int64 }
ActionOptions configures an explicit traditional POST adapter. Callbacks are borrowed, synchronous and may run concurrently for different requests. They must honor cancellation and must not mutate returned values during publication. There is no command registry, Provide, scope, transaction or event processing.
type ActionResult ¶
type ActionResult struct {
Response any
ValidationResults []validation.Result
Raw http.Handler
}
ActionResult is a traditional action's output, not a model-bound command outcome. Response is borrowed until publication; nil omits the response. ValidationResults are safe client-visible findings added by the action. Raw explicitly opts a successful action out of envelope publication and owns status, headers, body, and write errors through ordinary net/http semantics. Raw receives the request after body binding and must not retain the writer. Errors or blocking findings suppress both Response and Raw. No response is interpreted as an event or operation.
The action must return either Response or Raw, not both.
type Application ¶
type Application struct {
// contains filtered or unexported fields
}
Application is immutable compiled composition with explicit lifecycle. It must be constructed with Builder.Build; borrowed callbacks must support concurrency.
func (*Application) Catalog ¶
func (a *Application) Catalog() metadata.Catalog
Catalog returns copied declarations, including artifacts excluded from discovery.
func (*Application) Commands ¶
func (a *Application) Commands() commands.Pipeline
Commands exposes the backend command substitution seam.
func (*Application) Endpoints ¶
func (a *Application) Endpoints() []metadata.Endpoint
Endpoints returns a copied compiled Arc route table.
func (*Application) Queries ¶
func (a *Application) Queries() queries.Pipeline
Queries exposes the admitted backend query seam, including the optional queries.ObservablePipeline capability for the whole observation lifetime.
func (*Application) Run ¶
func (a *Application) Run(ctx context.Context, address string) error
Run listens on address and delegates owned lifecycle to Serve. The executable owns signal handling. Arc never installs signals, exits or replaces global logs.
func (*Application) Serve ¶
Serve takes ownership of listener, starts Arc and owns one http.Server. Context cancellation initiates shutdown with a fresh bounded budget. The server worker is joined before return; uncooperative callbacks can leave Arc Stopping.
func (*Application) ServeHTTP ¶
func (a *Application) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP requires explicit application startup before accepting work.
func (*Application) Shutdown ¶
func (a *Application) Shutdown(ctx context.Context) error
Shutdown atomically closes admission, cancels and joins observations (including failed opening cleanup), drains ordinary admitted work, and then stops hooks. Deadline expiration cancels active work but does not prove callbacks terminated; the application remains Stopping and a subsequent call can continue the join. Concurrent callers have independent wait budgets. No cleanup is detached.
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder is single-owner composition state, not concurrent-safe. Build is a single attempt: success is cached, failure is terminal. Borrowed registries must not be built independently or mutated after the root Build attempt.
func NewBuilder ¶
NewBuilder validates and copies configuration without activation or I/O.
Example ¶
package main
import (
"context"
"fmt"
"net/http/httptest"
"strings"
arc "github.com/cratis/arc.go"
"github.com/cratis/arc.go/commands"
)
type Greeting struct {
Name string `json:"name"`
}
func (c Greeting) Handle(context.Context) (string, error) { return "Hello, " + c.Name, nil }
func main() {
builder, err := arc.NewBuilder(arc.Options{})
if err != nil {
fmt.Println(err)
return
}
if err := commands.Register[Greeting](builder, commands.Handle(Greeting.Handle)); err != nil {
fmt.Println(err)
return
}
app, err := builder.Build()
if err != nil {
fmt.Println(err)
return
}
ctx := context.Background()
if err := app.Start(ctx); err != nil {
fmt.Println(err)
return
}
response := httptest.NewRecorder()
app.ServeHTTP(response, httptest.NewRequest("POST", "/api/greeting", strings.NewReader(`{"name":"Ada"}`)))
fmt.Println(response.Code)
if err := app.Shutdown(ctx); err != nil {
fmt.Println(err)
}
}
Output: 200
func (*Builder) AddLifecycle ¶
AddLifecycle registers a named borrowed hook before Build. Names are unique.
func (*Builder) AddTenantsProvider ¶
func (b *Builder) AddTenantsProvider(name string, factory Factory[TenantsProvider], keys ...di.Key) error
AddTenantsProvider registers a lazy resource-scoped discovery provider.
func (*Builder) AddUsersProvider ¶
func (b *Builder) AddUsersProvider(name string, factory Factory[UsersProvider], keys ...di.Key) error
AddUsersProvider registers a lazy resource-scoped discovery provider.
func (*Builder) Build ¶
func (b *Builder) Build() (*Application, error)
Build seals composition, validates metadata, and constructs frozen pipelines. No application provider, dependency, listener or business callback is activated.
func (*Builder) Commands ¶
Commands borrows the construction registry; do not Build it independently.
func (*Builder) ExpectGeneratedEndpoints ¶
ExpectGeneratedEndpoints records the complete endpoint expectations for owned artifact identities. Generated adapters call this before Build; proxy-only consumers must register the same expectations explicitly. Inputs are copied. Different registrations may share a profile fingerprint, but not identities. Build compares expectations with the final complete catalog before activation.
func (*Builder) Handle ¶
Handle registers a borrowed raw handler with normal ServeMux pattern syntax. Exact Arc/reserved-path ownership conflicts fail Build. Overlapping subtree and catch-all handlers are fallbacks: Arc routes and /.cratis/* always win.
Example (StaticFiles) ¶
ExampleBuilder_Handle_staticFiles composes public, nonpersonalized site content with a closure query. The helper below belongs to this example, not Arc's API.
package main
import (
"context"
"embed"
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"mime"
"net/http"
"net/http/httptest"
"net/url"
"path"
"strings"
"time"
arc "github.com/cratis/arc.go"
"github.com/cratis/arc.go/metadata"
"github.com/cratis/arc.go/queries"
)
//go:embed testdata/staticfiles/site/index.html testdata/staticfiles/site/assets/app.js testdata/staticfiles/site/assets/site.css
var staticSiteFiles embed.FS
type siteMessage struct {
Message string `json:"message"`
}
// ExampleBuilder_Handle_staticFiles composes public, nonpersonalized site content
// with a closure query. The helper below belongs to this example, not Arc's API.
func main() {
if err := runStaticSiteExample(); err != nil {
fmt.Println(err)
}
}
func runStaticSiteExample() error {
site, err := fs.Sub(staticSiteFiles, "testdata/staticfiles/site")
if err != nil {
return err
}
builder, err := arc.NewBuilder(arc.Options{})
if err != nil {
return err
}
message := siteMessage{Message: "Hello from Arc"}
if err := queries.Register[siteMessage](builder, "Current", queries.Function(
func(context.Context, queries.NoArguments) (siteMessage, error) {
return message, nil
}), queries.WithPath[queries.NoArguments]("/api/site-message"),
queries.WithAuthorization[queries.NoArguments](metadata.Authorization{AllowAnonymous: true})); err != nil {
return err
}
if err := builder.Handle("/", newStaticSite(site)); err != nil {
return err
}
app, err := builder.Build()
if err != nil {
return err
}
if err := app.Start(context.Background()); err != nil {
return err
}
server := httptest.NewServer(app)
requestErr := func() error {
client := server.Client()
client.Timeout = 5 * time.Second
for _, target := range []string{"/assets/app.js", "/app/tasks", "/api/site-message"} {
response, err := client.Get(server.URL + target)
if err != nil {
return err
}
_, readErr := io.Copy(io.Discard, response.Body)
closeErr := response.Body.Close()
if err := errors.Join(readErr, closeErr); err != nil {
return err
}
fmt.Println(target, response.StatusCode)
}
return nil
}()
cleanupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
serverErr := server.Config.Shutdown(cleanupCtx)
server.Close() // Join the external host before shutting down Arc.
return errors.Join(requestErr, serverErr, app.Shutdown(cleanupCtx))
}
// newStaticSite borrows an immutable embedded or trusted read-only ordinary FS.
// Open/Stat/Seek/Read/Close must finish promptly; fs.FS has no context contract.
// It is not a sandbox for writable directories, symlinks, or arbitrary FS code.
func newStaticSite(site fs.FS) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w = siteResponseWriter{w}
name, eligible := staticSiteName(r.URL)
if !eligible {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.Header().Set("Allow", "GET, HEAD")
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
serveStaticSiteFile(w, r, site, name)
})
}
func staticSiteName(u *url.URL) (string, bool) {
if u == nil || !strings.HasPrefix(u.Path, "/") || strings.ContainsAny(u.Path, "\\%") ||
u.EscapedPath() != (&url.URL{Path: u.Path}).EscapedPath() {
return "", false
}
if u.Path == "/" {
return "index.html", true
}
relative := strings.TrimPrefix(u.Path, "/")
if !fs.ValidPath(relative) {
return "", false
}
for _, segment := range strings.Split(relative, "/") {
if strings.HasPrefix(segment, ".") || strings.ContainsAny(segment, "\x00\r\n") {
return "", false
}
}
if strings.HasPrefix(relative, "assets/") {
return relative, true
}
if (relative == "app" || strings.HasPrefix(relative, "app/")) && !strings.Contains(relative, ".") {
return "index.html", true
}
return "", false
}
func serveStaticSiteFile(w http.ResponseWriter, r *http.Request, site fs.FS, name string) {
file, err := site.Open(name) // Open exactly the selected name once; no shell retry.
if err != nil {
staticSiteError(w, err)
return
}
defer func() {
if err := file.Close(); err != nil {
// Cleanup cannot retract a published response; never append HTML.
slog.Error("close static site file failed")
}
}()
info, err := file.Stat()
if err != nil {
staticSiteError(w, err)
return
}
if !info.Mode().IsRegular() {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
content, ok := file.(io.ReadSeeker)
if !ok {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
if _, err := content.Seek(0, io.SeekEnd); err != nil {
staticSiteError(w, err)
return
}
if _, err := content.Seek(0, io.SeekStart); err != nil {
staticSiteError(w, err)
return
}
contentType := mime.TypeByExtension(path.Ext(name))
if contentType == "" {
contentType = "application/octet-stream"
}
w.Header().Set("Content-Type", contentType)
// ServeContent owns HEAD/length and transfer semantics. Its later seek errors
// are redacted, and read/write failures never trigger a second response.
http.ServeContent(w, r, name, info.ModTime(), siteReadSeeker{content})
}
func staticSiteError(w http.ResponseWriter, err error) {
status := http.StatusInternalServerError
if errors.Is(err, fs.ErrNotExist) {
status = http.StatusNotFound
} else if errors.Is(err, fs.ErrPermission) {
status = http.StatusForbidden
}
http.Error(w, http.StatusText(status), status)
}
// No buffering or status interception: reapply only this site's no-store header
// because ServeContent removes Cache-Control on its own error responses.
type siteResponseWriter struct{ http.ResponseWriter }
func (w siteResponseWriter) WriteHeader(status int) {
w.Header().Set("Cache-Control", "no-store")
w.ResponseWriter.WriteHeader(status)
}
func (w siteResponseWriter) Unwrap() http.ResponseWriter { return w.ResponseWriter }
type siteReadSeeker struct{ io.ReadSeeker }
func (s siteReadSeeker) Seek(offset int64, whence int) (int64, error) {
position, err := s.ReadSeeker.Seek(offset, whence)
if err != nil {
return position, errors.New("static content unavailable")
}
return position, nil
}
Output: /assets/app.js 200 /app/tasks 200 /api/site-message 200
func (*Builder) Policies ¶
func (b *Builder) Policies() *authorization.Registry
Policies borrows the construction registry; do not Build it independently.
func (*Builder) RegisterCommand ¶
func (b *Builder) RegisterCommand(r commands.Registration) error
RegisterCommand implements the generated/manual command registrar seam.
func (*Builder) RegisterQuery ¶
func (b *Builder) RegisterQuery(r queries.Registration) error
RegisterQuery implements the query registrar seam.
func (*Builder) RegisterReadModel ¶
func (b *Builder) RegisterReadModel(r queries.ModelRegistration) error
RegisterReadModel implements the query registrar seam.
func (*Builder) Use ¶
func (b *Builder) Use(middleware ...Middleware) error
Use appends middleware, rejecting nil values and mutation after Build.
func (*Builder) Validators ¶
func (b *Builder) Validators() *validation.Registry
Validators borrows the construction registry; do not Build it independently.
type ConfigurationError ¶
ConfigurationError identifies a composition failure and preserves its cause.
func (*ConfigurationError) Error ¶
func (e *ConfigurationError) Error() string
Error describes the failed composition component.
func (*ConfigurationError) Unwrap ¶
func (e *ConfigurationError) Unwrap() error
Unwrap preserves inspectable configuration error identities.
type Factory ¶
Factory constructs a stage-local borrowed provider under an expiring scope view. Resource ownership stays with the configured operation resource opener.
type HTTPOptions ¶
type HTTPOptions struct {
MaxBodyBytes int64
MaxQueryBytes int
MaxResponseBytes int64
CorrelationHeader string
ReadHeaderTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
ShutdownTimeout time.Duration
QueryReaders []queries.RequestReader
}
HTTPOptions controls bounded unary HTTP publication and owned-server timeouts. Zero fields select defaults; embedded servers own their transport timeouts.
type IdentityOptions ¶
type IdentityOptions struct{ DetailsProvider string }
IdentityOptions selects a registered details provider when more than one exists.
type IntrospectionOptions ¶
IntrospectionOptions controls discovery exposure. Enabled controls only catalogs. Development defaults anonymous; other environments require authentication or leave discovery unmapped when no authentication adapter is registered.
type Lifecycle ¶
Lifecycle owns explicit startup work. Hooks must honor context and join all work they launch. Stop includes a failing Start hook and runs in reverse order. Borrowed clients are not closed unless explicitly registered by their owner.
type Middleware ¶
Middleware composes a handler once at Build. First registered is outermost. Constructors must not perform startup work; use Lifecycle for owned work. Middleware is trusted code; requests already carry correlation and receipt.
type ObservableOptions ¶
type ObservableOptions struct {
// MaxObservations bounds application query owners and hub subscriptions; default 1024.
// Opt-in QueryHealth has a separate private-pipeline ceiling of the same size.
MaxObservations int
// MaximumWait is the maximum first-result wait budget; default five minutes.
MaximumWait time.Duration
// CloseGrace bounds initial stream cleanup; default five seconds. Timeouts
// remain owned and must be joined by a later application Shutdown.
CloseGrace time.Duration
// WriteTimeout bounds each streaming write and flush; default ten seconds.
// The absolute unary HTTP write deadline is cleared while awaiting emissions.
WriteTimeout time.Duration
// MaxConnections defaults to 256 physical hub connections per application.
MaxConnections int
// MaxConnectionsPerOwner defaults to eight per verified subject/tenant;
// anonymous connections additionally use the actual peer IP, never forwarding headers.
MaxConnectionsPerOwner int
// MaxSubscriptions defaults to 64 outstanding operations per connection.
MaxSubscriptions int
// MaxOpenings defaults to 32 concurrent hub openings per application.
MaxOpenings int
// MaxOpeningsPerConnection defaults to four concurrent openings.
MaxOpeningsPerConnection int
// MaxQueryIDs defaults to 1024 retained IDs per connection, without eviction.
MaxQueryIDs int
// MaxOutboundJobs defaults to 64 pending/in-flight frames per connection.
MaxOutboundJobs int
// MaxQueuedBytes defaults to 32 MiB retained frames per connection.
MaxQueuedBytes int64
// MaxStreamingBytes defaults to 256 MiB retained hub frames per application.
MaxStreamingBytes int64
// KeepAliveInterval defaults to 30 seconds; hub SSE sends JSON Ping messages.
KeepAliveInterval time.Duration
// ConnectionLifetime defaults to twelve hours, bounding anonymous cookie lifetime.
ConnectionLifetime time.Duration
// AllowedOrigins adds exact origins to the default same-origin transport policy.
// The literal "null" explicitly permits opaque browser origins. No wildcards.
AllowedOrigins []string
// AnonymousOwner optionally supplies verified session ownership evidence.
// It is borrowed, concurrent, panic-protected and never authenticates a user.
// Nil uses an independent random HttpOnly cookie per anonymous SSE connection.
AnonymousOwner func(context.Context, *http.Request) (string, error)
}
ObservableOptions bounds owned query observations. Zero fields select defaults. Limits include opening and retired-but-unjoined operations, not just active streams. Hub limits cover retained framework state, not allocations in application callbacks.
type Options ¶
type Options struct {
// Diagnostics is borrowed, bounded backend recording; nil disables it.
Diagnostics *observability.Recorder
// QueryHealth enables protected aggregate health; nil leaves it absent in every environment.
QueryHealth *QueryHealthOptions
Namespace string
Environment string
Routes *metadata.Options
Authentication []authentication.Handler
Tenancy tenancy.Options
TenantResolver tenancy.Resolver
Membership tenancy.Membership
RequireTenant bool
Authorization authorization.Options
OpenResources execution.OpenResources
ScopeFactory di.ScopeFactory
DependencyCatalog di.Catalog
Clock func() time.Time
CleanupTimeout time.Duration
CommandOperations commands.OperationOptions // Separate cooperative recovery budget.
ExposeExceptionDetails bool
Logger *slog.Logger
HTTP HTTPOptions
Observable ObservableOptions
Introspection IntrospectionOptions
Identity IdentityOptions
}
Options configures composition. Collaborators are borrowed and must support concurrent calls. Configuration slices and pointed-to values are copied.
type QueryHealth ¶
type QueryHealth struct {
// Observations groups currently owned application observations by bounded label.
Observations []queries.ObservationHealth `json:"observations"`
// Hubs groups currently owned physical hub connections by fixed protocol.
Hubs []QueryHealthHub `json:"hubs"`
// Openings counts hub subscriptions still in admission/source opening.
Openings int `json:"openings"`
// Operations includes active and retired-but-unjoined hub subscriptions.
Operations int `json:"operations"`
}
QueryHealth is an aggregate native-owner sample. Application observations exclude this health query itself. Hub operations and observations overlap and must not be added together. Individual owner samples are synchronized, not globally atomic. No IDs, principal/client data, arguments, cached results or errors are included.
Example ¶
package main
import (
"context"
"fmt"
arc "github.com/cratis/arc.go"
"github.com/cratis/arc.go/identity"
"github.com/cratis/arc.go/queries"
)
func main() {
builder, err := arc.NewBuilder(arc.Options{
QueryHealth: &arc.QueryHealthOptions{Roles: []string{"Operations"}},
})
if err != nil {
panic(err)
}
application, err := builder.Build()
if err != nil {
panic(err)
}
if err := application.Start(context.Background()); err != nil {
panic(err)
}
// Backend code supplies a trusted identity. HTTP callers instead require an
// Authentication handler that verifies credentials and supplies this role.
ctx := identity.WithPrincipal(context.Background(), identity.System("Operations"))
result, err := queries.Perform[arc.QueryHealth](ctx, application.Queries(), arc.QueryHealthName, queries.Request{})
if err != nil {
panic(err)
}
health, present := result.Data()
fmt.Println(result.IsSuccess(), present, len(health.Observations))
if err := application.Shutdown(context.Background()); err != nil {
panic(err)
}
}
Output: true true 0
type QueryHealthHub ¶
type QueryHealthHub struct {
// Protocol is sse or websocket.
Protocol string `json:"protocol"`
// Connected counts connections without a cancellation signal.
Connected int `json:"connected"`
// Closing counts canceled connections still retained for joining.
Closing int `json:"closing"`
}
QueryHealthHub contains physical connection counts, not subscription counts.
type QueryHealthOptions ¶
type QueryHealthOptions struct {
// Roles is the administrative allowlist; any authenticated matching role grants access.
Roles []string
}
QueryHealthOptions explicitly enables aggregate health. Nil Options.QueryHealth exposes nothing, including in Development. Roles must contain at least one nonempty role; configuration is copied and any one authenticated role suffices.
type TenantsProvider ¶
TenantsProvider supplies display-only development tenants in provider order.
Source Files
¶
- action_handler.go
- action_request.go
- admission.go
- application.go
- builder.go
- catalog.go
- command_endpoint.go
- diagnostics.go
- diagnostics_http.go
- direct_sse.go
- direct_websocket.go
- discovery.go
- doc.go
- errors.go
- generated_contract.go
- hub_connections.go
- hub_security.go
- hub_sse.go
- hub_websocket.go
- identity_endpoint.go
- identity_registration.go
- ingress.go
- lifecycle.go
- logging.go
- middleware.go
- options.go
- publication.go
- query_endpoint.go
- query_health.go
- routes.go
- schema.go
- server.go
Directories
¶
| Path | Synopsis |
|---|---|
|
ContractTests
|
|
|
internal/browser/host
command
host is the loopback-only Arc fixture executable for the real-browser lane.
|
host is the loopback-only Arc fixture executable for the real-browser lane. |
|
internal/generatedconsumer
Package generatedconsumer is an executable model-bound authoring fixture.
|
Package generatedconsumer is an executable model-bound authoring fixture. |
|
internal/observables/clientfixture
Package clientfixture composes the real Arc host used by the locked client lane.
|
Package clientfixture composes the real Arc host used by the locked client lane. |
|
internal/observables/fixturehost
command
fixturehost is a loopback-only executable for the real Arc frontend contracts.
|
fixturehost is a loopback-only executable for the real Arc frontend contracts. |
|
internal/taskboard
Package taskboard is the Go-owned real-pipeline HTTP conformance fixture.
|
Package taskboard is the Go-owned real-pipeline HTTP conformance fixture. |
|
internal/taskboard/host
command
Command host runs the real-pipeline task-board conformance fixture on loopback.
|
Command host runs the real-pipeline task-board conformance fixture on loopback. |
|
Package arctest runs command, snapshot query and bounded observable query scenarios through real Arc pipelines without HTTP.
|
Package arctest runs command, snapshot query and bounded observable query scenarios through real Arc pipelines without HTTP. |
|
Package authentication runs ordered trusted authentication handlers.
|
Package authentication runs ordered trusted authentication handlers. |
|
Package authorization compiles explicit declarations and evaluates roles before constructing policies.
|
Package authorization compiles explicit declarations and evaluates roles before constructing policies. |
|
Package commands executes explicitly registered, model-bound Arc commands.
|
Package commands executes explicitly registered, model-bound Arc commands. |
|
Package concepts aliases Fundamentals.Go's portable UUID and calendar scalars.
|
Package concepts aliases Fundamentals.Go's portable UUID and calendar scalars. |
|
Package correlation normalizes correlation IDs and carries them through explicit contexts.
|
Package correlation normalizes correlation IDs and carries them through explicit contexts. |
|
examples
|
|
|
hosting
command
Command hosting runs a minimal Arc HTTP application without a container.
|
Command hosting runs a minimal Arc HTTP application without a container. |
|
nocontainer
command
Command nocontainer demonstrates Arc's foundation flows with plain Go wiring.
|
Command nocontainer demonstrates Arc's foundation flows with plain Go wiring. |
|
Package execution carries operation receipt and explicit execution metadata.
|
Package execution carries operation receipt and explicit execution metadata. |
|
Package identity provides immutable trusted principal snapshots and explicit context metadata.
|
Package identity provides immutable trusted principal snapshots and explicit context metadata. |
|
internal
|
|
|
httptransport
Package httptransport implements bounded unary HTTP input and encoding.
|
Package httptransport implements bounded unary HTTP input and encoding. |
|
logging
Package logging sanitizes untrusted values before handing them to host loggers.
|
Package logging sanitizes untrusted values before handing them to host loggers. |
|
modelshape
Package modelshape inspects data fields without invoking methods or getters.
|
Package modelshape inspects data fields without invoking methods or getters. |
|
pipeline
Package pipeline provides shared application callback and failure boundaries.
|
Package pipeline provides shared application callback and failure boundaries. |
|
streaming
Package streaming implements transport-independent bounded connection state.
|
Package streaming implements transport-independent bounded connection state. |
|
websockettransport
Package websockettransport isolates the vetted WebSocket implementation from the query pipeline and observable source API.
|
Package websockettransport isolates the vetted WebSocket implementation from the query pipeline and observable source API. |
|
wire
Package wire contains shared result-encoding mechanics, not public envelopes.
|
Package wire contains shared result-encoding mechanics, not public envelopes. |
|
Package metadata defines versioned, explicitly authored Arc artifact descriptors.
|
Package metadata defines versioned, explicitly authored Arc artifact descriptors. |
|
Package observability records bounded, payload-free backend diagnostics.
|
Package observability records bounded, payload-free backend diagnostics. |
|
Package observable provides independently owned query streams.
|
Package observable provides independently owned query streams. |
|
Package queries registers read-model-owned typed snapshots, binds caller inputs, and stages authorized execution, optional rendering and ordered interception.
|
Package queries registers read-model-owned typed snapshots, binds caller inputs, and stages authorized execution, optional rendering and ordered interception. |
|
Package serialization provides Arc JSON naming, null omission and presence-aware binding.
|
Package serialization provides Arc JSON naming, null omission and presence-aware binding. |
|
Package tenancy provides immutable tenant identifiers and explicit context metadata.
|
Package tenancy provides immutable tenant identifiers and explicit context metadata. |
|
Package validation defines Arc findings, typed validator invocation and command severity policy, explicit graph registries and portable model annotations.
|
Package validation defines Arc findings, typed validator invocation and command severity policy, explicit graph registries and portable model annotations. |