# modules/settings

```go
import "apistock.dev/modules/settings"
```

Package settings provides runtime settings: non-secret tunables declared in Go with a default and bounds, stored in PostgreSQL only when changed, and applied on every instance without a restart (ADR-0031).

Secrets and infrastructure (database URL, API keys, listen addresses) stay in the environment (ADR-0020); they can't be declared here.

	reg := settings.NewRegistry()
	codeTTL := settings.Duration(reg, "auth.verification_code_ttl", 15*time.Minute,
		settings.Describe("How long email verification codes stay valid."),
		settings.Range(5*time.Minute, time.Hour),
		settings.ReasonRequired(),
	)
	store, err := settings.NewStore(ctx, pool, reg, recorder)
	// Run store with the app's runners; pass codeTTL, a config.Value, to modules.

A [Store](#Store) loads every stored value at startup, applies changes made through [Store.Set](#Store.Set) and [Store.Reset](#Store.Reset) immediately, and learns about changes made by other instances through PostgreSQL LISTEN/NOTIFY, with a periodic full reload as a fallback.

Stability: pre-1.0 (ADR-0015).

## Constants

### const DefaultResyncInterval

```go
const DefaultResyncInterval = 5 * time.Minute
```

DefaultResyncInterval is how often a running [Store](#Store) reloads every value, covering notifications lost while disconnected.

## Variables

### var ErrUnknownSetting, …

```go
var (
	// ErrUnknownSetting reports a key that isn't declared in the registry.
	ErrUnknownSetting = errors.New("settings: unknown setting")

	// ErrVersionConflict reports that the setting changed after the caller
	// read it. Read it again and retry.
	ErrVersionConflict = errors.New("settings: setting changed since it was read")

	// ErrReasonRequired reports a change without a reason to a setting
	// declared with [ReasonRequired].
	ErrReasonRequired = errors.New("settings: a reason is required to change this setting")

	// ErrActorRequired reports a change without an authenticated actor in
	// the context.
	ErrActorRequired = errors.New("settings: changes require an authenticated actor")

	// ErrInvalidValue reports a value that fails decoding or validation.
	// The error is an [*InvalidValueError].
	ErrInvalidValue = errors.New("settings: invalid value")
)
```

Errors returned by [Store](#Store) methods. Check them with [errors.Is](https://pkg.go.dev/errors#Is).

### var Migrations

```go
var Migrations fs.FS = mustSub(migrationFiles, "migrations")
```

Migrations holds the module's goose migrations: the settings\_values and settings\_history tables. Apps copy them into db/migrations (the settings recipe does this); tests can apply them directly with pgtest.

## Types

### type Change

```go
type Change struct {
	// Version is the View.Version the caller last read.
	Version int64
	// Reason explains the change; required for ReasonRequired settings.
	Reason string
}
```

Change carries what a caller supplies with a change. The actor comes from the context.

### type HistoryEntry

```go
type HistoryEntry struct {
	ID        int64
	Key       string
	OldValue  json.RawMessage
	NewValue  json.RawMessage
	Version   int64
	Reason    string
	ActorKind actor.Kind
	ActorID   string
	RequestID string
	ChangedAt time.Time
}
```

HistoryEntry is one change to a setting. A nil value means the default.

### type InvalidValueError

```go
type InvalidValueError struct {
	Key    string
	Reason string
}
```

InvalidValueError describes why a value was rejected. Reason never contains the rejected value.

#### func (*InvalidValueError) Error

```go
func (e *InvalidValueError) Error() string
```


#### func (*InvalidValueError) Unwrap

```go
func (e *InvalidValueError) Unwrap() error
```

Unwrap returns [ErrInvalidValue](#ErrInvalidValue).

### type Kind

```go
type Kind string
```

Kind is a setting's value type.

#### const KindBool, …

```go
const (
	KindBool       Kind = "bool"
	KindInt        Kind = "int"
	KindFloat      Kind = "float"
	KindString     Kind = "string"
	KindEnum       Kind = "enum"
	KindDuration   Kind = "duration"
	KindStringList Kind = "string_list"
)
```

Setting kinds.

### type Option

```go
type Option interface {
	// contains filtered or unexported methods
}
```

An Option configures a setting declaration. An option that doesn't fit the setting's kind makes the declaration panic at startup.

#### func Describe

```go
func Describe(text string) Option
```

Describe sets the help text shown to operators.

#### func Email

```go
func Email() Option
```

Email requires a String setting, or each item of a StringList, to be a bare email address such as no-reply@example.com.

#### func Group

```go
func Group(name string) Option
```

Group sets the group used to organise settings in listings. Default: the key's first segment.

#### func MaxItems

```go
func MaxItems(n int) Option
```

MaxItems limits a StringList setting to n items.

#### func MaxLen

```go
func MaxLen(n int) Option
```

MaxLen limits a String setting, or each item of a StringList, to n characters.

#### func OneOf

```go
func OneOf(values ...string) Option
```

OneOf limits a String setting, or each item of a StringList, to values.

#### func Range

```go
func Range[T int | float64 | time.Duration](lo, hi T) Option
```

Range limits an Int, Float or Duration setting to \[lo, hi]. The bounds' type must match the setting: Range(0.0, 2.0) for a Float.

#### func ReasonRequired

```go
func ReasonRequired() Option
```

ReasonRequired makes every change to the setting require a reason, recorded in its history. Use it for security-relevant settings.

#### func RestartRequired

```go
func RestartRequired() Option
```

RestartRequired makes [Setting.Get](#Setting.Get) return the value loaded at startup. Changes are stored immediately and take effect when instances restart.

#### func URL

```go
func URL() Option
```

URL requires a String setting, or each item of a StringList, to be an absolute http or https URL.

#### func Validate

```go
func Validate[T any](fn func(T) error) Option
```

Validate adds a custom check. fn's parameter type must match the setting: func(time.Duration) error for a Duration. Error messages are shown to operators and must not include the value.

### type Registry

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

A Registry holds declared settings and their current values. Declare every setting before calling [NewStore](#NewStore). A Registry is safe for concurrent use.

#### func NewRegistry

```go
func NewRegistry() *Registry
```

NewRegistry returns an empty registry.

### type Setting

```go
type Setting[T any] struct {
	// contains filtered or unexported fields
}
```

A Setting is a typed handle to one declared setting. It implements [config.Value](/reference/config/#Value), so library modules can accept it for live options.

#### func Bool

```go
func Bool(r *Registry, key string, def bool, opts ...Option) *Setting[bool]
```

Bool declares a true/false setting.

#### func Duration

```go
func Duration(r *Registry, key string, def time.Duration, opts ...Option) *Setting[time.Duration]
```

Duration declares a duration setting, stored and edited as a Go duration string such as "15m" or "1h30m". Combine with [Range](#Range).

#### func Enum

```go
func Enum(r *Registry, key string, def string, allowed []string, opts ...Option) *Setting[string]
```

Enum declares a text setting limited to allowed values.

#### func Float

```go
func Float(r *Registry, key string, def float64, opts ...Option) *Setting[float64]
```

Float declares a decimal setting. Combine with [Range](#Range), passing float bounds such as Range(0.0, 2.0).

#### func Int

```go
func Int(r *Registry, key string, def int, opts ...Option) *Setting[int]
```

Int declares a whole-number setting. Combine with [Range](#Range).

#### func String

```go
func String(r *Registry, key string, def string, opts ...Option) *Setting[string]
```

String declares a text setting. Combine with [MaxLen](#MaxLen), [URL](#URL) or [Email](#Email).

#### func StringList

```go
func StringList(r *Registry, key string, def []string, opts ...Option) *Setting[[]string]
```

StringList declares a list-of-text setting, such as allowed origins. Get returns a copy the caller may modify.

#### func (*Setting[T]) Get

```go
func (s *Setting[T]) Get(context.Context) T
```

Get returns the setting's current value. It returns the default while the setting is unchanged, when its stored value fails validation, and before a store has loaded. Settings declared with [RestartRequired](#RestartRequired) keep the value loaded at startup. Get never touches the database.

#### func (*Setting[T]) Key

```go
func (s *Setting[T]) Key() string
```

Key returns the setting's key.

### type Store

```go
type Store struct {
	// contains filtered or unexported fields
}
```

Store persists settings in PostgreSQL and keeps a [Registry](#Registry) current. It is an app.Runner: run it so changes from other instances arrive. It is safe for concurrent use.

#### func NewStore

```go
func NewStore(ctx context.Context, pool *pgxpool.Pool, reg *Registry, recorder audit.Recorder, opts ...StoreOption) (*Store, error)
```

NewStore loads every stored value into reg and returns the store. After NewStore, reg accepts no new declarations. Changes are recorded as "settings.value.changed" audit events through recorder.

The settings tables come from [Migrations](#Migrations); apply them first.

#### func (*Store) DeleteHistoryBefore

```go
func (s *Store) DeleteHistoryBefore(ctx context.Context, before time.Time, limit int) (int64, error)
```

DeleteHistoryBefore deletes up to limit setting changes made before before, oldest first, and returns how many it deleted. Retention calls it until it deletes fewer than limit (ADR-0051).

#### func (*Store) Get

```go
func (s *Store) Get(key string) (View, error)
```

Get returns one setting, or [ErrUnknownSetting](#ErrUnknownSetting).

#### func (*Store) History

```go
func (s *Store) History(ctx context.Context, key string, before int64, limit int) ([]HistoryEntry, error)
```

History returns changes to key, newest first. For the next page, pass the last entry's ID as before (0 starts from the newest). limit is clamped to 1–100.

#### func (*Store) List

```go
func (s *Store) List() []View
```

List returns every declared setting in declaration order. It reads memory only.

#### func (*Store) OldestHistory

```go
func (s *Store) OldestHistory(ctx context.Context) (oldest time.Time, ok bool, err error)
```

OldestHistory returns when the oldest recorded change was made; ok is false when there are none.

#### func (*Store) Reload

```go
func (s *Store) Reload(ctx context.Context) error
```

Reload reads every stored value from the database.

#### func (*Store) Reset

```go
func (s *Store) Reset(ctx context.Context, key string, change Change) (View, error)
```

Reset returns key to its default. It returns the same errors as [Store.Set](#Store.Set), except for invalid values.

#### func (*Store) Run

```go
func (s *Store) Run(ctx context.Context) error
```

Run keeps the registry current until ctx is done, then returns nil. It listens on a dedicated connection for changes committed by any instance, reloads everything after connecting (changes made while disconnected send no notification), and reloads everything every resync interval as a fallback. Connection failures are logged and retried with backoff.

#### func (*Store) Set

```go
func (s *Store) Set(ctx context.Context, key string, value json.RawMessage, change Change) (View, error)
```

Set validates value (JSON, such as \`"30m"\` or \`42\`) and stores it for key. The change applies to this instance immediately and to others within moments. It returns [ErrUnknownSetting](#ErrUnknownSetting), an [\*InvalidValueError](#InvalidValueError), [ErrReasonRequired](#ErrReasonRequired), [ErrActorRequired](#ErrActorRequired) or [ErrVersionConflict](#ErrVersionConflict). Setting the current value again changes nothing.

#### func (*Store) UnknownKeys

```go
func (s *Store) UnknownKeys() []string
```

UnknownKeys returns stored keys that no declaration matches, for example settings removed from the code. Their rows are kept and ignored.

### type StoreOption

```go
type StoreOption interface {
	// contains filtered or unexported methods
}
```

A StoreOption configures [NewStore](#NewStore).

#### func WithLogger

```go
func WithLogger(logger *slog.Logger) StoreOption
```

WithLogger sets the logger for listener and invalid-value warnings. Default: discard.

#### func WithResyncInterval

```go
func WithResyncInterval(d time.Duration) StoreOption
```

WithResyncInterval sets how often [Store.Run](#Store.Run) reloads every value. Default: [DefaultResyncInterval](#DefaultResyncInterval).

### type View

```go
type View struct {
	Key         string
	Kind        Kind
	Group       string
	Description string

	// Value is the effective value as JSON; Default is the declared default.
	Value   json.RawMessage
	Default json.RawMessage
	// Modified reports whether a valid stored value overrides the default.
	Modified bool
	// InvalidStoredValue reports a stored value that fails validation, so
	// the default is in effect.
	InvalidStoredValue bool

	// Version increases with every change; pass it back to change the
	// setting. It is 0 for a setting that has never been changed.
	Version   int64
	UpdatedAt time.Time
	UpdatedBy string

	ReasonRequired  bool
	RestartRequired bool
	// RestartPending reports a restart-required setting changed since this
	// instance started.
	RestartPending bool

	// Constraints summarises validation, such as min, max, one_of, max_len,
	// max_items and format. Treat it as read-only.
	Constraints map[string]any
}
```

View is a setting's declaration and current state, for operator APIs.

