appx

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package appx builds a cobra-backed application context: build metadata, env-driven configuration, startup hooks, and shutdown.

Typical flow:

  1. NewAppContext(Main, options...) with WithMeta, optional WithRoot / WithPreInit / WithPreRun / WithServe / WithClose.
  2. AppCtx.Conf loads config from the environment (and optional config/local.yml), writes defaults under config/, runs WithPreInit, then initializes fields that implement confx types Init hooks.
  3. AppCtx.Execute dispatches cobra subcommands (`run`, `version`, or commands from AppCtx.AddCommand).
  4. AppCtx.Close on shutdown closes Conf components and WithClose hooks.

See example/appx for an end-to-end binary.

Meta and context

Meta holds Name / Feature / Version / CommitID / CommitAt / BuildAt / Runtime. NewAppContext starts from DefaultMeta; WithMeta replaces it. AppCtx.Conf injects Meta via WithAppMeta so nested Initializers (e.g. confotel) can read it with AppMetaFrom / MustAppMeta.

app := appx.NewAppContext(
	Main,
	appx.WithMeta(appx.Meta{Name: "demo", Version: "v1.0.0"}),
	appx.WithRoot("."),
)
app.Conf(ctx, &cfg)

Configuration

AppCtx.Conf accepts one or more config pointers. Each named type becomes an envx group (e.g. DEMO__OTEL): merge local.yml → decode env → write config/default.yml and config/.env → WithPreInit → types.InitByContext on fields. Anonymous structs are allowed only when a single configuration is passed.

Lifecycle hooks and shutdown

Duty boundary:

  • WithPreInit: make components able to Init (metadata, hooks)
  • Init (via AppCtx.Conf): resources ready, no business traffic
  • WithContext (via Conf inject): capabilities into ctx
  • WithPreRun: process-level prep with a complete ctx
  • WithServe: long-lived services (preferred for daemon apps)
  • Main (constructor arg): process owner after Serve starts

In AppCtx.Conf, after writing defaults: AppOption.PreInit → Init → inject. On `run`: AppOption.PreRun → AppOption.Serve (async) → Main:

  • WithPreInit: before Init in Conf (metadata, hooks, …)
  • WithPreRun: ordered process prep (global config, inject context, …)
  • WithServe: long-lived services (HTTP server, cron jobs, …)
  • Main: decides wait vs exit (signal / Serves done / oneshot); must AppCtx.Close. Framework does not infer Main from Serves emptiness.
  • WithClose: extra close hooks; Conf components close automatically in AppCtx.Close and do not need registration

Prefer serve-first: put long-lived work in WithServe; use AppCtx.AddCommand for oneshot CLI tools. Main still owns when to exit.

CLI

AppCtx.Execute runs the root cobra command from main. NewAppContext registers `run` (via AppCtx.AddCommand) and `version`.

Runtime

GetRuntime reads RuntimeEnvKey (RUNTIME_ENV): PROD / STAGING / DEV (default PROD). Stored on Meta.Runtime.

Example (Init_failed)
_ = os.Setenv("APP__SomeKey", "")

root := "./testdata2"
app := NewAppContext(
	_main,
	WithMeta(Meta{Name: "app"}),
	WithRoot(root),
)

defer os.RemoveAll(root)

defer func() {
	fmt.Println(recover())
}()

app.Conf(context.Background(), &struct {
	SomeKey MustInitFailed
}{})
Output:
failed to init [group:APP] [field:SomeKey]: must fail

Index

Examples

Constants

View Source
const RuntimeEnvKey = "RUNTIME_ENV"

RuntimeEnvKey is the environment variable read by GetRuntime.

Variables

View Source
var (
	// AppMetaFrom returns [Meta] from ctx if present.
	// [AppCtx.Conf] injects Meta before types.InitByContext so Initializers
	// (for example confotel) can resolve service name and version.
	AppMetaFrom = contextx.From[tCtxMeta, Meta]
	// MustAppMeta returns [Meta] from ctx or panics when missing.
	MustAppMeta = contextx.Must[tCtxMeta, Meta]
	// WithAppMeta stores [Meta] in ctx.
	WithAppMeta = contextx.With[tCtxMeta, Meta]
	// CarrierAppMeta builds a contextx.Carrier for [Meta].
	CarrierAppMeta = contextx.Carry[tCtxMeta, Meta]
)
View Source
var DefaultMeta = Meta{
	Name:     "name",
	Feature:  "branch",
	Version:  "version",
	CommitID: "commit",
	CommitAt: time.Now().Format(metaTimeLayout),
	BuildAt:  time.Now().Format(metaTimeLayout),
	Runtime:  GetRuntime(),
}

DefaultMeta is the baseline Meta used when WithMeta is omitted. Runtime is taken from GetRuntime at package init.

Functions

func BatchRun

func BatchRun(runners ...func())

BatchRun executes runners concurrently and waits for all to finish.

func BatchRunSync

func BatchRunSync(runners ...func())

BatchRunSync executes runners sequentially in order.

Types

type AppCtx

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

AppCtx is the application handle: cobra CLI, build meta, and env config groups.

func NewAppContext

func NewAppContext(main func() error, options ...Option) *AppCtx

NewAppContext builds an AppCtx with main as the `run` entry, a hidden root cobra command, and a `version` subcommand. Apply options before AppCtx.Conf.

On `run`, the framework runs PreRun → Serve → main. main decides process lifetime (wait for signal / Serves, or oneshot exit) and should call Close.

Example
root := "./testdata1"
app := NewAppContext(
	_main,
	WithRoot(root),
	WithMeta(Meta{
		Name:     "app",
		Feature:  "main",
		Version:  "v0.0.1",
		CommitID: "efbecda",
		CommitAt: "20060102150405",
		BuildAt:  "20060102150505",
		Runtime:  RUNTIME_DEV,
	}),
	WithServe(
		func() {
			time.Sleep(time.Second * 1)
			fmt.Println("batch runner 1")
		},
		func() {
			time.Sleep(time.Second * 2)
			fmt.Println("batch runner 2")
		},
	),
	WithPreRun(
		func() {
			time.Sleep(time.Second * 1)
			fmt.Println("pre runner 1")
		},
		func() {
			time.Sleep(time.Second * 2)
			fmt.Println("pre runner 2")
		},
	),
)

must.NoError(os.MkdirAll(filepath.Join(app.Root(), "config"), os.ModePerm))
must.NoError(os.WriteFile(filepath.Join(app.Root(), "config/local.yml"), []byte(`
APP__CONFIG1__Endpoint_Address: "postgres://hostname:5432/base"
APP__CONFIG1__WorkerID: "100"
APP__CONFIG2__ClientEndpoint_Address: http://localhost:80/demo
APP__CONFIG2__ServerPort: "8080"
`), os.ModePerm))

defer os.RemoveAll(root)

config1 := &Config1{}
config2 := &Config2{}
app.Conf(context.Background(), config1, config2)

cmd := app.cmd
buf := bytes.NewBuffer(nil)

cmd.SetOut(buf)
cmd.SetErr(buf)

{
	fmt.Println("exec `app version`")
	buf.Reset()
	cmd.SetArgs([]string{"version"})
	must.NoError(cmd.Execute())
	fmt.Println(buf.String())
}
{
	fmt.Println("exec `app run`")
	buf.Reset()
	cmd.SetArgs([]string{"run"})
	must.NoError(cmd.Execute())
	fmt.Println(buf.String())
}
Output:
exec `app version`
app:main@v0.0.1#efbecda(DEV)[commit=20060102150405|build=20060102150505]

exec `app run`
app:main@v0.0.1#efbecda(DEV)[commit=20060102150405|build=20060102150505]

name:      app
feature:   main
version:   v0.0.1
commit:    efbecda
commit_at: 20060102150405
build_at:  20060102150505
runtime:   DEV

APP__CONFIG1__Endpoint_Address=postgres://hostname:5432/base
APP__CONFIG1__Endpoint_Auth_DecryptKey=
APP__CONFIG1__Endpoint_Auth_Password=--------
APP__CONFIG1__Endpoint_Auth_Username=
APP__CONFIG1__Endpoint_Cert_CA=
APP__CONFIG1__Endpoint_Cert_Crt=
APP__CONFIG1__Endpoint_Cert_Key=
APP__CONFIG1__WorkerID=100
APP__CONFIG2__ClientEndpoint_Address=http://localhost:80/demo
APP__CONFIG2__ClientEndpoint_Auth_DecryptKey=
APP__CONFIG2__ClientEndpoint_Auth_Password=--------
APP__CONFIG2__ClientEndpoint_Auth_Username=
APP__CONFIG2__ClientEndpoint_Cert_CA=
APP__CONFIG2__ClientEndpoint_Cert_Crt=
APP__CONFIG2__ClientEndpoint_Cert_Key=
APP__CONFIG2__ServerPort=8080

pre runner 1
pre runner 2
batch runner 1
batch runner 2
main entry

func (*AppCtx) AddCommand added in v0.4.9

func (app *AppCtx) AddCommand(name, short string, runE func(args []string) error)

AddCommand registers a cobra subcommand under the app root.

name is the command Use string, short is the Short help text, and runE is invoked with the remaining CLI args when the command is selected. NewAppContext uses this to register `run`.

func (*AppCtx) Close added in v0.4.9

func (app *AppCtx) Close(ctx context.Context) error

Close shuts down the application.

It first closes components registered through AppCtx.Conf via types.CloseByContext (no WithClose needed for those), then runs any callbacks registered with WithClose. Errors are joined and returned.

func (*AppCtx) Conf

func (app *AppCtx) Conf(ctx context.Context, configurations ...any) context.Context

Conf loads one or more config pointers from the environment, writes default config files under AppCtx.Root/config, runs WithPreInit, then initializes fields that support types.InitByContext. Meta is injected into ctx via WithAppMeta before init.

Each named config type becomes an envx group (e.g. APP__OTEL). Anonymous structs are allowed only when a single configuration is passed.

func (*AppCtx) Execute added in v0.4.9

func (app *AppCtx) Execute() error

Execute runs the root cobra command (typically from main).

Dispatches to registered subcommands such as `run` / `version`, or any command added via AppCtx.AddCommand.

func (*AppCtx) Name

func (app *AppCtx) Name() string

Name returns the application name from build Meta.

func (*AppCtx) Root added in v0.5.2

func (app *AppCtx) Root() string

Root returns the path set by WithRoot, or the working directory used when constructing the app.

func (*AppCtx) Version

func (app *AppCtx) Version() string

Version returns the formatted build identity string from Meta.String.

type AppOption

type AppOption struct {
	*Meta
	// PreInits run sequentially before component Init in [AppCtx.Conf]: prepare
	// metadata or hooks so Init can succeed, without opening business traffic.
	PreInits []func()
	// PreRunners run sequentially before Serve: ordered process-level prep
	// with a complete ctx, such as global config or context injection.
	// Registered via [WithPreRun].
	PreRunners []func()
	// Serves run in parallel after PreRun: long-lived services such as
	// HTTP servers or scheduled jobs. Registered via [WithServe].
	Serves []func()
	// CloseFns are extra close callbacks for [AppCtx.Close], registered via
	// [WithClose]. Components from Conf are closed automatically and need not
	// be listed here.
	CloseFns []func() error
}

AppOption holds build Meta and startup hooks for an AppCtx.

func (*AppOption) PreInit added in v0.5.2

func (o *AppOption) PreInit()

PreInit runs WithPreInit callbacks sequentially before component Init.

func (*AppOption) PreRun

func (o *AppOption) PreRun()

PreRun runs WithPreRun callbacks sequentially. Invoked by `run` before AppOption.Serve.

func (*AppOption) Serve added in v0.5.2

func (o *AppOption) Serve()

Serve starts WithServe callbacks in parallel (non-blocking). Invoked by `run` after AppOption.PreRun.

type Meta

type Meta struct {
	Name     string  `json:"name"`
	Feature  string  `json:"feature"`
	Version  string  `json:"version"`
	CommitID string  `json:"commit"`
	CommitAt string  `json:"commit_at"`
	BuildAt  string  `json:"build_at"`
	Runtime  Runtime `json:"runtime"`
}

Meta is the build and runtime identity of an application.

Build-time fields (typically via -ldflags): Name, Feature, Version, CommitID, CommitAt, BuildAt. CommitID may carry a "-dirty" suffix when the working tree was dirty at build time. Runtime is filled from [RUNTIME_ENV] at process start and is not a build input.

func (*Meta) Overwrite

func (m *Meta) Overwrite(meta Meta)

Overwrite copies non-empty fields from meta onto m.

func (*Meta) Print

func (m *Meta) Print()

Print writes a colored multi-line Meta summary to stdout.

func (*Meta) String

func (m *Meta) String() string

String formats Meta as:

name:feature@version#commit(runtime)[commit=commitAt|build=buildAt]

func (*Meta) VersionString added in v0.4.9

func (m *Meta) VersionString() string

VersionString joins Name, Version, CommitID, and lowercase Runtime with "-".

type Option

type Option func(*AppCtx)

Option configures an AppCtx at construction time.

func WithClose added in v0.5.2

func WithClose(closes ...func() error) Option

WithClose registers extra close callbacks invoked by AppCtx.Close.

Closable components loaded via AppCtx.Conf do not need registration; they are closed automatically when AppCtx.Close runs.

func WithMeta added in v0.5.2

func WithMeta(meta Meta) Option

WithMeta sets the application Meta, replacing DefaultMeta.

func WithPreInit added in v0.5.2

func WithPreInit(fns ...func()) Option

WithPreInit appends ordered callbacks run before component Init in AppCtx.Conf.

Use to prepare the process so components can Init—for example registering metadata or hooks—without opening business traffic.

func WithPreRun added in v0.5.2

func WithPreRun(runners ...func()) Option

WithPreRun appends ordered callbacks run before WithServe on `run`.

Use for sequential process-level prep with a complete ctx—such as loading global config or injecting a global context—before long-lived services start.

func WithRoot added in v0.5.2

func WithRoot(root string) Option

WithRoot sets the application root used to resolve config/local.yml, config/default.yml, and config/.env. When empty, NewAppContext keeps the process working directory.

func WithServe added in v0.5.2

func WithServe(serves ...func()) Option

WithServe appends parallel callbacks run after PreRun on `run`.

Use for long-lived services such as HTTP servers or scheduled jobs.

type Runtime

type Runtime string

Runtime is the deployment environment label stored on Meta.

const (
	// RUNTIME_PROD is the production environment.
	RUNTIME_PROD Runtime = "PROD"
	// RUNTIME_STAGING is the staging environment.
	RUNTIME_STAGING Runtime = "STAGING"
	// RUNTIME_DEV is the local or development environment.
	RUNTIME_DEV Runtime = "DEV"
)

func GetRuntime

func GetRuntime() Runtime

GetRuntime returns the Runtime from RuntimeEnvKey, or RUNTIME_PROD when unset or unrecognized.

func (Runtime) String

func (r Runtime) String() string

String returns the runtime label.

Jump to

Keyboard shortcuts

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