arc

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 43 Imported by: 0

README

Arc for Go

Go Reference Build Release

The Go framework for Cratis Arc, bringing Arc's application-building approach to Go.

Status

Early development. Foundation packages provide artifact metadata, route resolution, UUID/temporal codecs, presence-aware JSON and result envelopes. Backend model-bound command/query pipelines now add authorization, validation, preparation, nested execution, paging, rendering and interception through typed adapters, with optional guarded DI resources. Releases will remain v0.x while the API is experimental. There is no tagged Go release yet; the installation command and Go reference will become usable after the first release.

See the compiling foundation example and feature-by-feature parity ledger. HTTP hosting composes commands, snapshots, observable SSE/WebSocket queries, identity and protected discovery with explicit graceful lifecycle. Try the minimal HTTP example and observable query guide. Adapter generation, TypeScript model/command/snapshot/observable-query proxies, and optional Chronicle/MongoDB integration are experimental. Proxy generation has actual CLI, independent Go-consumer, and locked Node runtime evidence, including a generated default WebSocket hub/Delta case. The wider manual transport lane remains separate. Those class-runtime cases use an independent delta reducer; a separate bounded mounted React lane executes generated hook reconstruction, cache reuse and joined cleanup with explicit WebSocket/Delta providers. Browser/DOM/StrictMode, the full Arc wrapper, suspense, reconnect, server paging, setter correctness and 30-second expiry remain unverified. MongoDB source-only watches have bounded native/live evidence and require explicit close/join/reopen after terminal loss; Chronicle watches remain unsupported. Opaque provider-source generation, including MongoDB Source[Find] without explicit emission metadata, remains unsupported. See the parity ledger for the Partial provider profiles and limits.

A separate paired snapshot HTTP checkpoint compares 36 GET/QUERY requests in 14 groups against pinned C# Arc: 34 strict matches and two exact successful ordinary-list paging-metadata allowances. This is Partial coverage, not command, authentication, HEAD, provider, streaming, browser or whole-product parity.

Installation

Requires Go 1.26 or later. Once a version has been published:

go get github.com/cratis/arc.go@latest

Use the lowercase module path exactly as shown. CI checks Go 1.26 and 1.27 independently of local Go workspaces.

Documentation

Start with Documentation. API reference will be available on pkg.go.dev after publication.

Development

From the repository root:

export GOWORK=off
go build ./...
go vet ./...
go test -race -count=1 -timeout=3m ./...
golangci-lint run

See CONTRIBUTING.md for the full checks and release conventions.

Community and security

License

MIT.

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

Examples

Constants

View Source
const QueryHealthName queries.FullyQualifiedQueryName = "QueryHealth.ObserveHealth"

QueryHealthName is the protected framework query's backend and hub identity.

Variables

View Source
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 identifies a shape requiring an explicit schema override.
	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

func (a *Application) Serve(ctx context.Context, listener net.Listener) error

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.

func (*Application) Start

func (a *Application) Start(ctx context.Context) error

Start joins a single startup attempt. ctx controls startup only, not application or request lifetime after success. Restart after stop or failure is unsupported.

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

func NewBuilder(options Options) (*Builder, error)

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

func (b *Builder) AddLifecycle(name string, participant Lifecycle) error

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) Catalog

func (b *Builder) Catalog() metadata.Catalog

Catalog returns an independent combined declaration snapshot.

func (*Builder) Commands

func (b *Builder) Commands() *commands.Registry

Commands borrows the construction registry; do not Build it independently.

func (*Builder) ExpectGeneratedEndpoints

func (b *Builder) ExpectGeneratedEndpoints(profile string, endpoints []metadata.Endpoint) error

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

func (b *Builder) Handle(pattern string, handler http.Handler) error

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) Queries

func (b *Builder) Queries() *queries.Registry

Queries 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

type ConfigurationError struct {
	Component, Name string
	Cause           error
}

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

type Factory[T any] func(context.Context, *execution.Scope) (T, error)

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

type IntrospectionOptions struct {
	Enabled               *bool
	RequireAuthentication *bool
	Roles                 []string
}

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

type Lifecycle interface {
	Start(context.Context) error
	Stop(context.Context) error
}

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

type Middleware func(http.Handler) http.Handler

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

type TenantsProvider interface {
	ProvideTenants(context.Context) ([]tenancy.Tenant, error)
}

TenantsProvider supplies display-only development tenants in provider order.

type UsersProvider

type UsersProvider interface {
	ProvideUsers(context.Context) ([]identity.User, error)
}

UsersProvider supplies display-only development users in provider order.

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.

Jump to

Keyboard shortcuts

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