Services

Each builtin service lives in its own module, std.services.<name>: import it and call it, there is no instance to construct. The ADM service manager starts the automatic services a program imports before application.new runs and stops them on shutdown, so calling one is just Cache.get("k"). For the language rules behind service declarations, see Services in the docs.

A service's public surface is exactly its annotated methods. Fields are always private, and a def with no annotation is internal to the service: it can be called by the service's own methods (method "helper" is internal to service Counter, field "count" is internal to service Counter). Only the members the manager relies on are exempt: the ServiceDescriptor fields (description, dependencies, lifecycle, permissions, status) and the ServiceHooks methods (config, start, stop, restart). Every public method is one of three kinds:

query Reads a snapshot of the service's state. Never async.
commit Mutates state and emits a typed event. Implicitly atomic; may be async.
emit Describes an event's shape only - you subscribe to it with @on(Service.method) and never call it.
usage.adm
use std.services.cache::(Cache)
use std.services.diagnostics::(Diagnostics)
use std.metrics::(Counter)

def warm(email string) !none {
	Cache.set("user.{email}", email, ttl = 5m)

	let hits = try Diagnostics.register<Counter>("cache.warm")
	hits.inc()
	return none
}

@on(Cache.expired)
def onExpiry(evt Event<string>) {
	println("expired {evt.result}")
}

Events and observers

Every @commit() call emits an event; there is no way to run a commit silently. The event is named after the method, carries the arguments it was called with, and carries what it returned. It is an ordinary value, Event<T> from the prelude:

builtin
struct Event<T> {
	source service           // the service that emitted it
	name   string            // the method name: "open"
	params map<string, any>  // the call's named arguments: {"path": "data.txt"}
	result T                 // the method's return value, exactly its declared type (T, ?T or !T)
	time   time.Time         // reserved; not populated yet
}

So Storage.open("data.txt") hands the caller the file and hands every observer an Event<os.File> with name = "open", params["path"] = "data.txt" and result set to that same file. An @emit() method is the secondary form: the service calls it from inside a commit (opened(file)) to publish a named event whose result is the value it was given and whose params are empty. An event emitted inside a commit reaches its handlers after the outermost commit has completed, behind that commit's own event; @emit(immediate = true) runs the handlers at the call instead, which is how a request event gets its answer filled in before the emitting code reads it. @query() methods never emit. Emitting an event that no method declares is a compile error.

@on(Service.method) subscribes a handler: a free function or a method, taking exactly one argument, the Event<T> for that method's return type. Handlers run synchronously inside the commit, after the method body returns and before the caller gets the result, so by the time Storage.open returns every observer has seen the event. A handler cannot change the commit's result. A second argument filters: @on(Storage.open, audit) with def audit(ev Event<os.File>) bool runs the handler only for events the filter accepts; the filter sees the same event record. A method handler on a service runs on that service's singleton; a method handler on any other type runs on one lazily created instance of it.

The Event is read straight through its fields: ev.result, ev.params["path"], ev.name. Commits are serialized per service (implicitly atomic), so observers only ever see completed state, and events are not stored: a handler registered after a commit does not see it.

The service manager owns lifecycle. Services whose lifecycle.start is StartMode.Automatic are started before application.new runs, in the order their dependencies require; StartMode.Manual services wait for code to call start(). lifecycle.restart decides what a failed start gets: Never, OnFailure (retried) or Always (retried more), and the outcome lands in the service's status field as a ServiceStatus (Starting, Started, Stopping, Stopped, Restarting, Failed). Calling start/stop/ restart yourself goes through the manager too, so status stays truthful. Services are singletons that never expire and cannot be constructed with new; all of them stop at shutdown.

Service labs

Every service has a lab: a test bench that sets the conditions its @commit and @query calls meet, so you can watch your program under a slow, failing or absent service without touching the service or your code.

lab.adm
Storage.lab.delay(150ms)                            // every call waits first
Storage.lab.delay(20ms, method = "read", jitter = 30ms)
Storage.lab.refuse("disk full", method = "write")   // fails before it runs
Storage.lab.refuse("flaky", rate = 0.2)             // one call in five
Storage.lab.refuse("busy", times = 3)               // the next three calls
Network.lab.offline()                               // every call that can fail does
Network.lab.online()
Storage.lab.calls("write")                          // calls made so far
Storage.lab.reset()

A delay holds the calling task before the method runs. A refusal ends the call with an error holding your message; it needs a method that can fail (!T), and one that cannot is only delayed. rate draws from the lab's own sequence, the same on every run, so a test that refuses one call in five refuses the same calls each time. Conditions stay until reset, each service has its own lab, and a call that needs a permission is refused it before a lab holds it. A lab meets calls of the service: code that reaches a file or a socket without calling the service is not affected.

What only the service itself can simulate, a disk that is 85% full or a corrupt read, is a struct of conditions the service declares and names in its own lab. The fields are typed, so a wrong name or value is a compile error; their defaults mean nothing is simulated, and reset puts them back.

conditions.adm
struct DepotConditions {
    // Storing fails once the depot is this full; 0 turns it off.
    fullAt float = 0.0
}

service Depot {
    lab = new ServiceLab<DepotConditions>()

    @commit()
    def put(share float) !float {
        fail "no space left" when lab.conditions.fullAt > 0.0 && used + share >= lab.conditions.fullAt
        ...
    }
}

// in a test
Depot.lab.conditions.fullAt = 0.6

Labs are for tests and the IDE: only a test build (adm test) and a program the IDE runs have them (the build constant __labs__). Any other build, debug or --release, has none: its services run no lab code, and a rule set there stops the program with a message. Outside its service a lab is named only in a test file (_test.adm); anywhere else Storage.lab is a compile error. The IDE sets the labs of the program it runs from its service panel.

The lab's methods can be called from any task at any time, and every read of conditions holds what it read, so a reset under a call in flight takes nothing from it. The fields themselves are an object's like any other: set them while no other task is calling the service.

Services also ship annotations - @cache, @metric, @measureTime and friends - that wrap a declaration instead of being called. Import them by name from the service's module (use std.services.cache::(cache)). An annotation that accepts more than one kind of target must be written qualified (use std.services.debugger then @debugger.trace()), because a by-name import narrows it to a single target.

Signatures on this page are generated from the standard library source. Some services are still filling in method bodies; annotations whose behavior is not wired up yet are marked planned.

Builtin services

Application

Identity and lifecycle of the running application.

  • querydef files(name string = "") !string

    The folder holding a package's shipped files, its [package] files: the calling package's when name is empty, else the library named namespace:name (local:name for one installed from a file). Once the program is installed that is share/<namespace>/<name> beside the executable, or the install root for the application itself; while it runs from its source tree it is the package cache for a library and the tree for the application. Fails when no folder exists in either layout.

  • querydef filePath(path string) !string

    Resolves a package path: package://assets/logo.png is a file the calling package shipped, acme:imaging://assets/logo.png one a library shipped. Any other path comes back unchanged. Of the folders a package's files can be in, the first that holds the path is taken, so a program started with adm run finds the files of its source tree. Every Storage method, and so every std.os call, resolves its path this way, so os.readFile("package://assets/logo.png") works from adm run, from a build in the tree and from an installed program alike. Fails when the package has no shipped files.

  • querydef pid() int

    Returns process id of the application

  • querydef executablePath() string

    Returns path to the application executable.

  • querydef startTime() Time

    Returns time when the application process started.

  • querydef name() string

    Returns the name the application is known by: the adm.toml slug when one is configured, else the declared application name.

  • commitdef home() !string

    Returns the application's home folder, creating it on first use and bringing it up to date with this version: the version marker, a snapshot of data/ and the @migration chain. Fails on a downgrade without @allowDowngrade, on a foreign folder at the same path, or when no home directory is known. See Application home & migrations.

  • querydef version() string

    Returns application version, or an empty string if the application is not versioned.

  • querydef homePath() !string

    Returns the per-user folder this application owns, creating it on first use. Secrets (Vault), plugins, configuration and caches live under it, so an application never writes anywhere else. It sits in the platform's application-data location, named after the app:

    Linux $XDG_DATA_HOME/<name>, default ~/.local/share/<name> macOS ~/Library/Application Support/<name> Windows %APPDATA%\<name>

    ADM_APP_HOME, when set, names the folder directly (portable installs, tests). A folder that already exists there with contents but no version marker belongs to something else and is refused, never adopted or overwritten.

    The first call also brings the folder up to date with this version:

    version last version that used the folder data/ the application's own files backup/<ver>/ snapshot of data/ taken before migrating from <ver>

    A folder last used by an older version has data/ snapshotted to backup/<old>/, then every @migration step newer than <old> runs in order; a failing step restores the snapshot and fails this call. A folder last used by a newer version fails unless the application is annotated @allowDowngrade. Fails when no home directory is known (HOME, or USERPROFILE on Windows). The path home() would use, without creating or upgrading the folder. For code that reads an optional file there (the Policy service's policy.toml) and must not leave a folder behind.

  • querydef build() string

    Returns application build number, or an empty string if unavailable.

  • querydef instanceId() string

    Returns identifier of the current application instance.

    The identifier is unique to this running instance and changes between runs.

  • querydef arguments() string[]

    Returns the command-line arguments the application was started with, the program's own path first.

  • querydef uptime() duration

    Returns how long the application has been running.

  • commitdef shutdown(code int = 0) !none

    Requests graceful application shutdown: shutdownRequested is emitted, so observers can finish their work, then the running services are stopped, each before the services it names as hard dependencies, and the process exits with the code. An interrupt (Ctrl-C) or terminate signal requests it with code 128 + the signal's number; a second such signal ends the process at once.

  • querydef shuttingDown() bool

    Returns whether graceful application shutdown has been requested.

  • emitdef shutdownRequested(code int) int

    Emitted when graceful application shutdown is requested, before the services are stopped. Handlers that are not async have run to their end when the services stop.

  • commitdef exit(code int)

    Terminates the application using the specified exit code.

Browser

The page a web build (adm build --os wasm) runs in: its address and history, the document's metadata, cookies, storage, permissions, installing the site as an app and its service worker. Off the web every call but available() fails with BrowserError.Unavailable; a refusal says which: Unsupported, Denied, or NeedsUserGesture for a call that must follow a click or key press. Module std.services.browser.

  • querydef available() bool

    Whether the program runs in a browser.

  • querydef info() !BrowserInfo

    The browser, its version and engine, the platform it runs on and the user's languages, preferred first.

    Fails off the web.

  • querydef location() !url.URL

    The page's address.

    Fails off the web.

  • commitdef navigate(to url.URL, replace bool = false) !none

    Goes to another address, leaving the program; replace leaves no history entry behind. In-app navigation is the Router's.

    Fails off the web.

  • commitdef back() !none

    Goes one entry back in the tab's history; forward() one forward, reload() loads the page again, which starts the program afresh.

    Fails off the web.

  • emitdef locationChange(to url.URL) url.URL

    The history entry changed: back or forward, or in-app navigation.

  • querydef metadata() !PageMetadata

    What the document's head holds: title, description, canonical address, sharing picture, language, robots, theme colour and further <meta> pairs. setMetadata writes them; an empty field removes what the head had for it.

    Fails off the web.

  • commitdef confirmLeave(on bool) !none

    Makes the browser ask before the tab is closed or navigated away (unsaved changes), or stop asking.

    Fails off the web.

  • querydef cookies() !Cookie[]

    The site's cookies the page can read (not the HttpOnly ones); cookie(name) one of them, setCookie and removeCookie write. Needs the adm.browser.cookies permission.

    Fails off the web, or when the policy denies cookies.

  • querydef storage(key string, session bool = false) !?string

    The value under key in the site's storage (localStorage, or sessionStorage with session); setStorage and removeStorage write. Needs adm.browser.storage.

    Fails off the web, or when the policy denies storage.

  • querydef permission(name string) !PermissionState

    The browser's answer for a permission: "camera", "notifications", "clipboard-read", ...

    Fails off the web, or when the browser does not know the permission.

  • commitdef install() !bool

    Offers to install the site as an app when installable(); true when the user accepted.

    Fails off the web, when the policy denies install, when the browser offers no install now, or when the call does not follow a click or key press.

  • commitdef registerWorker(script url.URL) !none

    Registers the service worker that serves the site offline; updateReady is emitted when a new version waits, and applyUpdate() makes it take over.

    Fails off the web, or when the browser refuses the worker.

  • commitdef evaluate(script string) !string

    Runs JavaScript in the page and returns its result as JSON, for what the calls above do not cover. Needs adm.browser.script.

    Fails off the web, when the policy denies script, or when the script throws.

Cache

Fast in-memory cache with TTL, idle expiration, glob selectors, and size limits.

Lab conditions (Cache.lab.conditions, see Service labs). The defaults change nothing.

clockSpeed float How fast time passes for expiry: at 60.0 a ttl of a minute runs out in a second, at 0.0 nothing expires.
maxEntries ?int The most entries the cache holds, whatever CacheConfig allows; the least recently used are evicted at once.
maxMemory ?int The most memory the cache holds, in bytes, in the same way.
missRate float The share of value reads that find nothing though the key is there; 1.0 is a cache that never answers.
dropWrites bool set stores nothing.
cache_lab.adm
Cache.lab.conditions.clockSpeed = 3600.0   // an hour a second
Cache.lab.conditions.maxEntries = 2        // full: evicts down to two
Cache.lab.conditions.missRate = 0.25       // one read in four misses
  • commitatomic def set(key string, value any, ttl ?duration = none, idle ?duration = none)

    Stores a value under the key.

    ttl caps how long the key lives; idle is how long it may go unread before it expires. Either may be left off.

  • queryatomic def get(key string) ?any

    The value under the key, or none when there is none or it has expired.

    Reading restarts the idle timer.

  • queryatomic def get(keys string[]) map<string, any>

    The values under the given keys; missing and expired keys are left out.

    Reading restarts each key's idle timer.

  • queryatomic def get(selector Glob) map<string, any>

    The values under every key the selector matches.

    Reading restarts each key's idle timer.

  • commitatomic def delete(key string)

    Removes the key.

  • commitatomic def delete(keys string[])

    Removes the keys.

  • commitatomic def delete(selector Glob) int

    Removes every key the selector matches and returns how many went.

  • commitatomic def renew(key string, ttl ?duration = none, idle ?duration = none)

    Gives the key a new ttl, idle time, or both.

    A ttl counts from now; an idle time restarts the idle timer.

  • commitatomic def renew(selector Glob, ttl ?duration = none, idle ?duration = none) int

    Renews every key the selector matches and returns how many.

    A ttl counts from now; an idle time restarts each idle timer.

  • commitatomic def take(key string) ?any

    Removes the key and returns what it held, or none when nothing live was there.

  • commitatomic def touch(key string)

    Restarts the key's idle timer; the ttl is untouched.

  • commitatomic def touch(keys string[])

    Restarts the idle timer of each key; ttls are untouched.

  • commitatomic def touch(selector Glob) int

    Restarts the idle timer of every key the selector matches and returns how many.

  • queryatomic def has(key string) bool

    Whether the key is live.

  • queryatomic def has(keys string[]) map<string, bool>

    Whether each key is live.

  • queryatomic def has(selector Glob) bool

    Whether any live key matches the selector.

  • queryatomic def ttl(key string) ?duration

    How long the key has left to live, or none when it is gone or has no ttl.

  • queryatomic def ttl(keys string[]) map<string, ?duration>

    How long each key has left to live; none for a key that is gone or has no ttl.

  • queryatomic def ttl(selector Glob) map<string, ?duration>

    How long each matching key has left to live; none for one with no ttl.

  • queryatomic def idle(key string) ?duration

    How long the key may still go unread, or none when it is gone or has no idle time.

  • queryatomic def idle(keys string[]) map<string, ?duration>

    How long each key may still go unread; none for a key that is gone or has no idle time.

  • queryatomic def idle(selector Glob) map<string, ?duration>

    How long each matching key may still go unread; none for one with no idle time.

  • queryatomic def keys() string[]

    Every live key.

  • queryatomic def keys(selector Glob) string[]

    Every live key the selector matches.

  • queryatomic def size() int

    How many keys are live.

  • queryatomic def size(selector Glob) int

    How many live keys the selector matches.

  • commitatomic def clear()

    Empties the cache.

  • emitdef expired(key string) string

    Emitted when a key expires.

  • emitdef deleted(key string) string

    Emitted when a key is removed by a call rather than by expiry.

  • emitdef created(key string) string

    Emitted when a key is stored for the first time.

Database

Named SQL database connections, each a pool shared between tasks. Import with use std.services.database::(Database); the driver for each URL comes from an adm.database.* library.

  • querydef add(name string, target string, limit int = 8, versions string = "") !none

    Registers a connection under a name: a pool of up to limit connections to the database the URL names, opened as statements need them. Then migrates the database: the @migration(db = ...) steps on the connection that it has not seen run in order, each in a transaction (all of them on a database with no version yet), under a lock in the database so instances starting together migrate once; then the stored routines installed on it are created where they changed. The version is kept in the home folder, or with versions in that table of the database. Fails, and keeps no connection, when a step or routine fails or a newer version of the program migrated the database (unless @allowDowngrade).

  • querydef add(target string) !none

    Registers and migrates the default connection, the one get() returns.

  • queryatomic def versionsTable(name string) ?string

    The table a connection's migrations keep its version in; none when it is kept in the home folder.

  • queryatomic def get(name string) !db.Pool

    The connection registered under a name.

  • queryatomic def get() !db.Pool

    The default connection: the one added without a name, else the only one registered.

  • queryatomic def names() string[]

    The registered names, the default's as "".

  • querydef exec(statement string, args ...any) !db.Result

    Runs a statement on the default connection.

  • querydef query(statement string, args ...any) !db.Rows

    Runs a statement on the default connection and returns its rows.

  • querydef remove(name string) !none

    Closes a connection's pool and forgets its name.

Clipboard

Text copy and paste through the system clipboard. The service drives the desktop's clipboard program: wl-copy and wl-paste on Wayland, xclip or xsel on X11, pbcopy and pbpaste on macOS; on Windows it uses the system clipboard itself. A machine without one reports nothing available and the calls fail. Reading needs adm.clipboard.read, writing adm.clipboard.write, and a clipboard program runs through Runtime.run, so adm.runtime.spawn as well. copy, paste and clear are commits, so @on(Clipboard.copy), @on(Clipboard.paste) and @on(Clipboard.clear) observe them. The programs run through the shell, which the OS sandbox refuses. A program with a window open uses the window system's clipboard instead: std.ui answers the reading and writing events for calls made on the task that runs the windows.

  • commitdef copy(text string) !none

    Puts the text on the clipboard, replacing what was there.

    Fails when the policy denies it, this machine has no clipboard program, or the program fails.

  • commitdef paste() !string

    Returns the text on the clipboard, "" when it holds none.

    Fails when the policy denies it, this machine has no clipboard program, or the program fails.

  • commitdef clear() !none

    Empties the clipboard.

  • querydef has() !bool

    Whether the clipboard holds any text. Needs the read permission.

  • querydef capabilities() ClipboardCapabilities

    Reports whether this machine has a clipboard program and which one: wl-clipboard, xclip, xsel or pbcopy; windows for the Windows clipboard, browser on the web.

  • commitdef watch(period duration = 500ms) !none

    Starts watching the clipboard: a task reads it every period and emits changed when the text differs from what it saw last, whichever program changed it. Calling it while watching only changes the period.

    Fails when the policy denies reading or this machine has no clipboard program.

  • commitdef unwatch()

    Stops the watcher task; nothing happens when none runs.

  • querydef watching() bool

    Whether the watcher task runs.

  • emitdef changed(change ClipboardChange) ClipboardChange

    Emitted by the watcher when the clipboard text changed. ClipboardChange carries the text ("" when emptied), whether this program put it there through copy, and when the watcher noticed.

  • emitdef reading(request ClipboardRequest) ClipboardRequest

    Emitted before the clipboard is read, inside paste and has: a handler that has the clipboard at hand calls request.answer(text). Unanswered, the clipboard program is used.

  • emitdef writing(request ClipboardRequest) ClipboardRequest

    Emitted before the clipboard is written, inside copy and clear: a handler that put request.text on the clipboard ("" empties it) calls request.done(). Unanswered, the clipboard program is used.

Config

Layered configuration: one or more sources merged by precedence, typed reads, profiles, overrides for the running process, and events for what changed. Later sources win over earlier ones, and a source that is not optional must load.

A key is a path of names joined by dots, compared without case and without the - and _ inside its names: db.maxConnections, db.max_connections and DB.MAX-CONNECTIONS are one key, so a file and an environment variable can name the same value. A key that names no value but has keys below it reads as the map of those.

A profile is a set of keys a source keeps under profiles.<name>. (a TOML [profiles.dev.db] table): while the profile is active they replace the same source's plain keys, and they are left out otherwise.

A typed read converts the value to the type asked for: numbers, bool and duration also from their text (what an INI file or an environment variable holds), an enum member from its name, a list also from the texts between the commas of a text, a map<string, V>, and a struct or type from the keys below the key, by its fields' names. Reads answer from memory and never wait for a source that is loading.

config.adm
use std.services.config::(Config, ConfigConfig, SourceConfig, FileSource, EnvSource)

try Config.config(
	ConfigConfig{
		sources: [
			SourceConfig{source: new FileSource("app.toml")},
			SourceConfig{source: new FileSource("local.toml"), optional: true},
			SourceConfig{source: new EnvSource("APP_")},          // APP_DB_HOST is db.host
		],
	}
)
try await Config.start()

let port = try Config.get<int>("server.port")       // ?int: none when no source defines it
let db = try Config.get<Database>("db")             // a struct from the keys below db
Config.set("feature.flag", true)                    // an override, this run only
try Config.switchProfile("dev")                     // [profiles.dev.db] now replaces [db]
ConfigConfig.profile ?string The active profile; none loads only the plain keys.
ConfigConfig.sources SourceConfig[] The sources, lowest precedence first.
SourceConfig{source ConfigSource, optional bool = false} A source and whether the service tolerates it failing to load: an optional source that fails keeps what it last loaded.
interface ConfigSource name() string, distinct within one configuration, and load() !map<string, any>: every key the source holds, as dotted keys or as maps nested by name. load must not call Config.
interface WatchableConfigSource watch(changed def ()) !none and unwatch() !none: a source that reports its own changes; the service reloads when changed is called.
FileSource(path string, format ConfigFormat = ConfigFormat.Auto) A file, read at each load and watched while the service is started (also when an editor replaces it). ConfigFormat: Auto (told by the name), Toml, Json, Ini (section.key), Env (.env files: DB_HOST is db.host).
EnvSource(prefix string = "", separator string = "_") The environment variables whose name starts with the prefix, without it: the rest in lower case, each separator a dot. With "__", APP_DB__MAX_CONNECTIONS is db.max_connections.
adm.network.etcd Source(client, prefix, format = ValueFormat.Text, name = "") A library's source for a remote store: the keys under a prefix of an etcd cluster, the names after it joined by dots (/config/db/host under /config/ is db.host), watched while the service is started. ValueFormat.Json reads each value as a JSON document.
ConfigChange{key, oldValue ?any, newValue ?any, source ?string} One changed key: none for a value that was not there or is gone; the name of the source the new value comes from, none for an override.
ConfigReloadError{source ?string, error error} A source that failed to load, with the error it raised.
Config.config(val ConfigConfig) !none Applies the profile and the sources. On a started service the new sources are loaded at once and watched in place of the old ones; on one that is not, they are loaded by start. Fails when a source has no name or two have the same one, and on a started service as reload does.
Config.start() / stop() start loads every source and starts watching the ones that can be watched; it fails when a source that is not optional cannot be loaded, when a @config variable that is not optional and has no default finds no value for its key, and when a value does not read as the type of the @config variable it fills. stop ends the watching; the values stay readable.
  • commitdef switchProfile(profile ?string) !none

    Switches to the profile and reloads every source.

    When the reload fails the old profile and its values stay in place. Fails as reload does.

  • querydef profile() ?string

    The active profile, or none when no profile is selected.

  • querydef get(key string) ?any

    The value the key resolves to, or none when no source defines it. A key that names no value but has keys below it gives those as a map<string, any>.

  • querydef get<T>(key string) !?T

    The value the key resolves to, converted to T; none when no source defines it.

    Fails when the value does not read as a T or does not fit it.

  • querydef has(key string) bool

    Whether some source or an override defines the key, or a key below it.

  • commitdef set(key string, value any)

    Overrides the key for this process.

    An override wins over every source and is never written back to one. A map overrides the keys below the key, one for each of its values.

  • commitdef reset(key string)

    Drops the override on the key and on the keys below it.

    The key goes back to whatever the sources say.

  • commitdef reload() !none

    Reloads every source.

    Fails when a source that is not optional cannot be loaded, and as start does for the program's @config variables: the values of before then stay. A source that fails is emitted as reloadFailed whether or not it is optional.

  • querydef sources() SourceConfig[]

    The sources, highest precedence first.

  • querydef source(name string) ?SourceConfig

    The source with that name, or none.

  • querydef sourceOf(key string) ?ConfigSource

    The source the key's value comes from; none for an override or an unknown key.

  • querydef lastLoaded() time.Time

    When the sources last loaded without error; the zero time before that.

  • emitdef changed(change ConfigChange) ConfigChange

    Emitted when a key's value changes: by a reload, a change of profile or of sources, set or reset. The first load of a run emits every key it defines.

    A reload that leaves the value as it was emits nothing.

  • emitdef reloadFailed(event ConfigReloadError) ConfigReloadError

    Emitted when a source fails to load.

    The values from the last good load stay in place.

Debugger

CPU, memory and heap profiles, call traces, heap snapshots and @watch. Profiles and traces need a debug build.

  • commitdef profileStart(kind ProfileKind) !ProfileId

    Starts a profile of the given kind and returns its id. A CPU profile samples every running task's stack 100 times a second; a Memory profile records the stack and size of every allocation; a Heap profile records every allocation until it is freed, so it ends with what is still live of what was allocated while it ran.

    Fails when a profile of that kind already runs, when the kind is GPU (not implemented yet), on a platform without profiling signals (CPU), and in release builds.

  • commitdef profileStop(id ProfileId) !Profile

    Stops the profile and returns what it collected.

    Fails when id names no running profile.

  • commitdef traceStart() !TraceId

    Starts recording the calls of every @trace function and method, and returns the trace's id.

    Fails when a trace already runs, and in release builds.

  • commitdef traceStop(id TraceId) !Trace

    Stops the trace and returns what it recorded.

    Fails when id names no running trace.

  • queryplanneddef stackTrace() errors.StackTrace

    The stack trace of the calling task.

  • querydef heapSnapshot() !HeapSnapshot

    Takes a heap snapshot: the allocations live now and the bytes they hold, and, while a Heap profile runs, its live allocations by stack.

  • commitplanneddef pause() !none

    Breaks into the debugger when one is attached; otherwise does nothing.

  • emitdef watched(event WatchEvent) WatchEvent

    Emitted when a watched field or variable is read or written: the event names it, says whether it was a store, carries the value read or stored, the value a store replaced, where the access is written, the task and the time.

    Which accesses count is the watch's mode. A handler that touches a watched field or variable itself reports nothing.

Devices

Cameras, microphones, GPUs, USB, serial ports, GPS receivers and other attached hardware. Linux only for now. Listing needs adm.devices.list; the permissions camera, microphone, input, volume, serial and location are required by the std.devices functions that use them. A sound card's node is its control node (/dev/snd/controlC4), which holds its mixer. A serial port (serial:ttyACM0) is a Gps when its USB ids are a GPS maker's; reading a GPS takes location alone, whichever receiver or port.

  • querydef list(kind ?DeviceKind = none) !Device[]

    Every connected device, or only those of one kind.

    Fails when the policy denies listing the devices.

  • querydef gpus() Device[]

    The GPUs, one per render node (/dev/dri/renderD128), in node order. Listing them needs no permission: std opens one itself to decode video on it.

  • querydef get(id string) !?Device

    The device with that id, or none.

    Fails when the policy denies listing the devices.

  • commitdef watch(period duration = 1s) !none

    Starts watching for devices connecting and disconnecting; connected/disconnected fire for the changes. On Linux the kernel's device events trigger a new listing; without them the devices are listed again every period. The devices already there when watching starts are not reported.

    Fails when already watching or the period is not positive.

  • commitdef unwatch() !none

    Stops watching.

    Fails when not watching.

  • querydef watching() bool

    Whether the watching task runs.

  • emitdef connected(device Device) Device

    Emitted when a device appears.

  • emitdef disconnected(device Device) Device

    Emitted when a device goes away.

  • commitdef open(id string) !int

    Opens the device's node for reading and writing and returns its descriptor for Storage.read/write/control/map/wait. Opening asks no permission of its own: the modules that drive a kind of device (std.devices.openCamera, openMicrophone) require the kind's permission themselves, and check its device rules (DeviceSpec) against the id. A speaker's or microphone's node never makes the open wait: one another program holds fails at once.

    Fails when there is no such device, it has no node, or the node cannot be opened.

  • querydef opened(fd int) ?Device

    The device a descriptor open returned belongs to, none for one it did not return or that is closed. Holding the descriptor is what tells a program about the device, so this asks no permission.

  • commitdef close(fd int) !none

    Closes a descriptor open returned. Fails when it is not open.

Diagnostics

Named metric registry - counters, gauges, histograms, and toggles.

  • commitdef register<T: Metric>(name string, labels map<string, string> = {}, unit string = "", description string = "") !T

    Registers a metric under the specified name.

    labels tell the series of one metric apart: register<Counter>("http.requests", {"route": "/orders"}) once per route. unit says what the value counts (ms, By, 1) and description what it means; both belong to the name, and the first one given for a name stays.

    If a metric with the same name, labels and type already exists, it is returned. Fails if the name and labels are already registered with a different metric type.

  • querydef get(name string, labels map<string, string> = {}) ?Metric

    Returns the metric registered under the specified name and labels.

    Returns none if no such metric exists.

  • querydef has(name string, labels map<string, string> = {}) bool

    Returns whether a metric with the specified name and labels is registered.

  • querydef list() map<string, Metric>

    Returns all registered metrics by name; one with labels is under name{key="value",...}, the labels sorted by key.

  • querydef names() string[]

    Returns the names list holds the metrics under.

  • querydef series() MetricSeries[]

    Returns every registered metric with its name, labels, unit, description and metric, in the order they were registered.

Features

Asks a server which features this installation gets and answers from memory: kill switches, gradual rollouts per place, attribute gates and A/B groups. The server decides everything and signs its answer; the program never receives the rules. The server is std.net.features (FeatureServer), or any server that speaks its exchange.

Reads never wait for the server and never fail: a name the server did not mention, a program that has no reply yet, and a reply past its validity all give the fallback written in the code. The last reply is kept in the Vault (features.assignments), so a program that starts offline has what it had until the validity the server set (30 days by default). The service asks again as often as the server says (an hour by default); a kill switch reaches a program at its next request, there is no push.

A reply is taken only when its Ed25519 signature verifies with FeaturesConfig.key, it was made for this installation and it is not past its validity; otherwise what was held stays and failed is emitted.

The request carries the vault's id (Vault.id()), the program's name, version, system and processor, the locale and whatever set added: what personal data goes into set is the program's choice. A telemetry collector and a feature server run by the same operator can join what they receive on that id.

The service asks whether or not the user agreed to telemetry, so kill switches and rollouts reach everyone. An A/B group takes only installations whose use is measured (Telemetry is collecting), unless the group is for everyone; a user who withdraws consent leaves running tests at the next request, and results describe consenting users only. The first read of a feature or of a group is a Telemetry usage event (feature with feature and enabled, group with group and arm), sent under Telemetry's own consent.

features.adm
use std.services.features::(Features, FeaturesConfig)

try Features.config(FeaturesConfig{url: "https://features.example.com/", key: serverKey})
try await Features.start()

Features.set("plan", "pro")                                    // an attribute the rules can match

if Features.enabled("new-checkout") { ... }                    // a gate, a kill switch, a rollout
let limit = Features.value<int>("upload.limit", 10)            // a feature's value
let arm = Features.arm("login-page")                           // the A/B arm, "" outside the test
let color = Features.value<string>("login-page", "button-color", "blue")

Features.override("new-checkout", true)                        // this run only
FeaturesConfig.url string Where the feature server answers: the URL the request is posted to.
FeaturesConfig.key ?Ed25519PublicKey The public half of the key the server signs with.
FeaturesConfig.retry duration = 1min How long to wait before the first reply says how often to ask, and after a request that failed; twice as long after each further failure, up to the server's interval.
FeaturesConfig.timeout duration = 10s How long one request may take.
FeaturesConfig.tls ?tls.Config TLS settings for an https server; none trusts the system's roots.
FeaturesConfig.headers map<string, string> Sent with every request: an API key the server asks for.
FeaturesConfig.name, version string The program's name and version as the server sees them; the application's own when empty.
FeaturesConfig.locale string The user's locale (fr-FR), for rules on locales; when empty, the language the I18n service has in use (the program's, else the system's).
FeaturesConditions Lab conditions (Features.lab.conditions): unreachable, badSignature, stale, fetchDelay.
  • commitdef config(val FeaturesConfig) !none

    Applies the settings; on a started service the next request, made at once, goes to the new server, and a reply held from a server with another key is dropped. Fails when retry or timeout is not positive, and when the service is started and the settings have no url or no key. Needs the permission adm.features.fetch.

  • commitdef set(key string, value string)

    Adds an attribute to every request from now on, for the server's rules to match, and asks the server again in the background. An empty value takes the attribute away.

  • querydef enabled(name string, fallback bool = false) bool

    Whether the feature is on for this installation; fallback when the server did not mention it, no reply has arrived yet, or the reply held is past its validity.

  • querydef value<T>(name string, fallback T) T

    The feature's value as a T: a number, a text, a bool, a struct read from an object, an array. fallback when the server gave the feature no value or one that is not a T, and whenever enabled would give its fallback.

  • querydef arm(group string) string

    The arm of the group this installation is in; "" outside the test, for a group the server did not mention, and without a reply that holds.

  • querydef value<T>(group string, key string, fallback T) T

    The value of one key of a group: what the installation's arm gives the key, the group's default outside the test, fallback for a group or a key the server did not mention.

  • querydef refresh() !none

    Asks the server now and takes its reply. Fails with a FeaturesError (kind: Transport, Reply, Signature, Subject, Expired) and keeps what it held.

  • commitdef override(name string, enabled bool, value ?any = none)

    Makes this run answer enabled and value for the feature as given, whatever the server says: a developer's switch, a command-line flag, a test. Not kept between runs, not reported to Telemetry.

  • commitdef override(group string, arm string, values map<string, any> = {})

    Makes this run answer arm for the group as given, and value for the keys in values; the other keys stay as the server gave them.

  • commitdef restore(name string = "")

    Takes back the overrides of the feature and of the group of that name; every override for "".

  • emitdef changed(change FeaturesChange) FeaturesChange

    Emitted when a reply gives a feature or a group something else than the reply before it: features and groups hold the names concerned.

  • emitdef failed(failure FeaturesError) FeaturesError

    Emitted when a request fails or its reply is refused; what was held stays.

I18n

Translates the program's texts and writes numbers, money, dates and times the way the user's region writes them.

The language, the region and the time zone are three settings, as an operating system has them. The language picks the catalog translated text comes from; the region picks the formats; the time zone picks the wall clock an instant is shown on. English text with Romanian formats is setLanguage("en") and setRegion("RO"). Each starts as the system's.

Catalogs are files of std.formats.translations (.po), one for each language, loaded from a folder at start and with load later. A text is a message in the ICU MessageFormat syntax (std.i18n.Message): {count, plural, one{# item} other{# items}}. It is looked up in the catalog of the language asked for, then in those of the languages close to it (de for de-AT); when none has it the answer is the source text, and missing reports it once.

A library ships its own catalogs in a locales folder of its [package] files. A text asked for by a library's code, or by a @translate field a library declares, is looked up in the library's catalogs first, then in the application's, so an application can translate a library into a language the library does not ship.

A server answers each request in its user's language, region and time zone: setLanguage, setRegion and setTimeZone with I18nScope.Task set them for the calling task and the tasks it starts, and the methods also take them as arguments. Texts and formats are read without starting the service; without catalogs every text is its source text.

i18n.adm
use std.services.i18n::(I18n, I18nConfig, I18nScope)

try I18n.config(I18nConfig{folder: "locales"})          // locales/de.po, locales/fr.po, ...
try await I18n.start()

I18n.text("basket.count", {"count": 3})                 // "3 Artikel"
I18n.money(1999, "EUR")                                 // "19,99 €"
I18n.date(time.now())                                   // "12.10.2026"

try I18n.setLanguage("en")                              // English text ...
try I18n.setRegion("ES")                                // ... with Spanish formats
try I18n.setTimeZone("Europe/Bucharest")                // instants on Bucharest's clock

// A server handler: this request's language and region, for the code it calls.
let asked = I18n.negotiate(acceptLanguage)                // "de-CH, de;q=0.9, en;q=0.5"
try I18n.setLanguage(asked.str(), I18nScope.Task) unless asked is none
I18n.text("basket.title", {}, "fr")                     // or by argument
I18nConfig.language string = "" The language translated text is shown in (de, pt-BR, de_AT); empty is the system's.
I18nConfig.region string = "" The region whose formats numbers, money, dates and times take: a region code in capitals (RO, the formats of its main language) or a locale (en-GB); empty is the system's.
I18nConfig.timeZone string = "" The time zone instants are written in: an IANA name (Europe/Bucharest) or an offset (+02:00); empty is the system's.
I18nConfig.folder string = "" The folder start loads the application's catalogs from, one file for each language named by it (de.po, pt_BR.po); a relative path is looked for among the application's shipped files. Empty loads locales when the application ships one.
I18nConfig.source string = "en" The language the program's own texts are written in. A user it serves is shown those texts, and nothing is reported missing for them.
I18nScope Application (the whole program) or Task (the calling task and the tasks it starts).
I18nChange language Locale, region Locale, timeZone TimeZone: all three as they are after a change.
MissingText key, context, language Locale, package (the library whose code asked, empty for the application).
SystemLocales language Locale, languages Locale[] (most preferred first), region Locale, timeZone TimeZone.
  • commitdef config(val I18nConfig) !none

    Applies the settings. A started service loads the folder again when it changed, and emits changed when the language or the region in use did.

    Fails when language, region or source is not a well-formed locale, and on a started service as start does; the service then stays as it was.

  • querydef language() Locale

    The language translated text is shown in: the calling task's when it set one, else the program's, else the system's.

  • querydef region() Locale

    The locale whose formats numbers, money, dates and times take, resolved the same way.

  • commitdef setLanguage(language string, scope I18nScope = I18nScope.Application) !none

    Sets the language; empty goes back to the system's, or for a task to the program's. For the program it emits changed when the language in use becomes another.

    Fails when the language is not a well-formed locale.

  • commitdef setRegion(region string, scope I18nScope = I18nScope.Application) !none

    Sets the region: a region code in capitals (RO) or a locale (en-GB); empty goes back to the system's, or for a task to the program's.

    Fails when the region is neither a region code nor a well-formed locale.

  • querydef timeZone() TimeZone

    The time zone instants are written in: the calling task's when it set one, else the program's, else the system's.

  • commitdef setTimeZone(timeZone string, scope I18nScope = I18nScope.Application) !none

    Sets the time zone: an IANA name or an offset; empty goes back to the system's, or for a task to the program's. For the program it emits changed when the zone in use becomes another. A ZonedDateTime is always written in its own zone, and a plain date or time has none.

    Fails when the zone is neither in the tz database nor an offset.

  • querydef system() SystemLocales

    What the operating system says the user prefers, from the POSIX locale variables (LANGUAGE, LC_ALL, LC_MESSAGES, LANG, LC_NUMERIC, LC_TIME, LC_MONETARY); American English when it says nothing.

  • querydef languages() Locale[]

    The languages the application has catalogs for, and its source language.

  • querydef negotiate(acceptLanguage string) ?Locale

    The language of languages() that best serves an HTTP Accept-Language header; none when nothing is close.

  • commitdef load(path string) !none

    Loads a catalog file, or every catalog file of a folder. A file's language is the one it names inside, else the one in its name (de.po, messages.fr-CA.po). The catalogs become those of the package that calls.

    Fails when the path cannot be read, a file is not a well-formed catalog, or a file names no language.

  • commitdef load(catalog Catalog) !none

    Loads a catalog made in code. Fails when its locale is undetermined.

  • querydef text(key string, params map<string, any> = {}, language string = "", region string = "", context string = "") string

    The translated text of a message with the values of params written in: in language when given, else the language in use; numbers and dates in it as region writes them when given, else the region in use. context tells apart messages with the same key.

    When no catalog has the message, the answer is the key with the values written in, and missing reports it.

  • querydef textIn(owner string, key string, fallback string, params map<string, any> = {}, language string = "") string

    The same for the code of the module owner, whose library's catalogs are looked in first; fallback is the answer when no catalog has the message. @translate fields read through it.

  • querydef has(key string, language string = "", context string = "") bool

    Whether a catalog has the message in the language or one close to it.

  • querydef number(value int | float | Decimal, region string = "") string

    The number as the region writes it: 1.234,5.

  • querydef percent(value float | Decimal, region string = "") string

    A fraction as a percentage: 0.25 is 25 %.

  • querydef money(amount Decimal | float, currency string = "", region string = "") string

    An amount in a currency (ISO 4217), the region's own when none is given: 1.234,50 €.

  • querydef money(minorUnits int, currency string = "", region string = "") string

    An amount given in the currency's minor units: 1999 with EUR is 19.99 euros.

  • querydef date(value Time | ZonedDateTime | Date | DateTime, width DateTimeStyle = DateTimeStyle.Medium, region string = "", timeZone string = "") string

    The day: 12.10.2026, or longer or shorter by width. An instant is written in the time zone in use, or in timeZone when given (instants only); a zoned date-time in its own.

  • querydef time(value Time | ZonedDateTime | TimeOfDay | DateTime, width DateTimeStyle = DateTimeStyle.Short, region string = "", timeZone string = "") string

    The time of day: 14:05.

  • querydef dateTime(value Time | ZonedDateTime | DateTime, dateWidth DateTimeStyle = DateTimeStyle.Medium, timeWidth DateTimeStyle = DateTimeStyle.Short, region string = "", timeZone string = "") string

    The day and the time: 12.10.2026, 14:05.

  • querydef spellOut(value int | float | Decimal, ruleSet string = "%spellout-numbering", region string = "") string

    The number in words, or through another rule set of the region's language (%spellout-ordinal, %digits-ordinal).

  • querydef plural(count int | float | Decimal, kind PluralType = PluralType.Cardinal, language string = "") PluralCategory

    The plural form the count takes in the language in use, or in language when given: One for 1 in English, Few for 3 in Romanian. The form belongs to the language of the text it picks; the region has no say in it.

  • emitdef changed(change I18nChange) I18nChange

    Emitted when the program's language, region or time zone becomes another. Interface code shows its texts again on it; a task's own language and region emit nothing.

  • emitdef missing(text MissingText) MissingText

    Emitted the first time a text is asked for that no catalog has in the language asked for or one close to it, for each key, context, language and package.

IPC

Remote interfaces between processes, on one machine or several. A program calls the methods of an interface and another process runs them. The interface and the types in its signatures live in a library both programs import; the serving process marks a type @remote<I> and passes an instance to provide, and the calling process gets its own value of I from connect<I>. Every method of a remote interface is errorable and not generic, and everything it takes and returns can cross to another process; the build checks this where the interface is named. Permissions: adm.ipc.connect and adm.ipc.listen.

orders.adm
// shared by both programs
interface Orders {
	def place(order Order) !Receipt
	def cancel(id int) !none
}

// the server
@remote<Orders>
type Warehouse { ... }

try IPC.provide(new Warehouse())
try IPC.listen("0.0.0.0:7400", Listener{tls: config, secrets: [secret]})

// the client
let desk = try IPC.connect<Orders>("orders.internal:7400", Auth{secret: secret})
let receipt = try desk.place(Order{customer: "ada", item: "desk", quantity: 2})

TCP speaks TLS 1.3 unless plain is set on both sides; a Unix socket (unix:/path) is plain unless given a TLS config. A client proves itself with a shared secret (answered as HMAC over the server's challenge and the TLS channel, never sent), a token the server's verifyToken checks (only over TLS or a Unix socket), a client certificate, or on a Unix socket its user id (Listener.users). IPC.caller() tells a served method who called it. Many calls run at once on one connection, each on its own task on the server; cancelling the waiting task, or Link.timeout, cancels the call on the server too. With several addresses, Link.balance picks the server of each call (RoundRobin, Random, Pinned); a server that cannot be reached or stops answering pings is set aside and tried again later. A call the server never took moves to another server; one that was sent and lost its link fails with Lost. Dropping the last reference to the value connect returned closes its connections. A client keeps calling a server whose version of the interface still has every method and type the client uses, so servers can be upgraded first with additive changes. adm audit lists the interfaces a package provides and the ones it calls.

Clusters. IPC.join(Cluster{...}) makes a process a node of a cluster: nodes find each other through a Discovery (StaticNodes, DNSNodes, or IPCNodes of the Redis, PostgreSQL and etcd libraries), then keep each other's state by gossip over their own IPC links, so the store is only needed to start. A node silent too long is set aside, then dropped; one that leaves says so. A client given one node's address learns the others and spreads its calls over them (Link.follow). A node busier than refuseAbove sends a call to a less busy member before running it, and the client follows without the caller noticing; after forceAfter refusals the least busy node is told to take the call. IPC.members() lists the members, and the joined and left events report changes.

  • commitdef provide<T>(instance T) !none

    Serves instance to other processes, for every interface its type is marked @remote for, on every listener open now or later. Its methods are called from many tasks at once.

    Fails when another instance already serves one of those interfaces.

  • commitdef withdraw<T>(instance T) !none

    Stops serving instance. Calls already running finish; new ones are answered as if the interface was never provided.

    Fails when it is not provided.

  • commitdef listen(address Endpoint, options Listener = Listener{}) !string

    Starts accepting clients on address: host:port, [v6]:port or unix:/path. Returns the address bound, with the port the system chose for port 0.

    Fails when the address cannot be bound, when a TCP address has neither a TLS certificate nor plain, or when the listener asks clients for no proof on an address other machines can reach and open is not set.

  • querydef close(address string) !none

    Stops accepting clients on address and closes its connections once their running calls finish.

    Fails when nothing listens there.

  • querydef connect<I>(address Endpoint, auth Auth = Auth{}, options Link = Link{}) !I

    Returns an I whose methods run in the process at address.

    Fails with an IPCError: Unreachable when the server cannot be reached, Refused when it does not accept auth, Mismatch when it provides another version of I or none.

  • querydef connect<I>(addresses string[], auth Auth = Auth{}, options Link = Link{}) !I

    Returns an I served by several processes; options.balance decides which one takes each call. Succeeds when one server accepts.

    Fails with an IPCError: Unreachable when none can be reached, Refused or Mismatch when a reachable one refuses the client or provides another version of I.

  • querydef disconnect<I>(client I) !none

    Closes the connections behind a value connect returned. Calls in flight fail with IPCErrorKind.Closed.

    Fails when client did not come from connect.

  • querydef join(cluster Cluster) !none

    Makes this process a node of a cluster: it serves gossip on its listeners, announces itself to cluster.discovery, learns the other members, tells its clients of them, and sends calls to less busy members above refuseAbove. Listen first.

    Fails when this process is a node already, or when discovery cannot record it.

  • querydef leave() !none

    Leaves the cluster: the other members are told, discovery forgets this node.

    Fails when this process is in no cluster.

  • querydef members() Member[]

    The members of the cluster as this node knows them, itself first: address, interfaces, state, load.

  • emitdef joined(member Member) Member, def left(member Member) Member

    A member became alive; a member left or fell silent too long.

  • querydef caller() ?Caller

    Who is calling, inside a method a client called; none anywhere else.

  • querydef links() LinkInfo[]

    The connections open now, accepted and made.

  • querydef listeners() string[]

    The addresses being listened on.

Logging

Leveled logging with structured fields, to the writers you configure.

A write call returns nothing and never fails: it puts the record in a queue with its time and the place it was called from, and one task encodes the records and hands them to the writers in the order they were written, so a slow disk or collector never holds the caller. Each writer has an encoder and the levels it takes. With no writers configured, records go to standard error in the console form (ConsoleEncoder: colored on a terminal, plain when redirected). In a program started from the IDE, the service panel shows the level, the writers (where each writes, its encoder and its levels) and the latest 200 entries, colored by level. When the program ends the records still waiting are written and the writers closed.

logging.adm
use std.services.logging::*

try Logging.config(
	LogConfig{
		level:   LogLevel.Debug,
		writers: [
			LogWriterConfig{writer: new ConsoleWriter(), encoder: new TextEncoder()},
			LogWriterConfig{
				writer:  new FileWriter("logs/app.log", LogRotation{period: 24h, keep: 14, compress: true}),
				encoder: new TextEncoder(),
			},
			LogWriterConfig{
				writer:  new NetworkWriter("logs.example.com:5170"),
				encoder: new JSONEncoder(),
				levels:  [LogLevel.Warning, LogLevel.Error, LogLevel.Fatal],
			},
		],
	}
)

Logging.info("user signed in", {"user": user.id})
// [Mon Oct 12 14:05:09.250000 2026] [billing:info] [pid 4242] session.adm:17: user signed in user=42
EncoderWrites
TextEncoder(offset = 0s, source = true, pid = true)One line in the form of the Apache error log: time, [module:level], [pid n], file:line:, the message, then the fields as name=value sorted by name. offset shifts the time east of UTC.
ConsoleEncoder(stream = ConsoleStream.Stderr, offset = 0s, source = LogSource.Debug)One line for a person at a terminal, in color: 14:05:09.250 ERROR billing amount=12 user=ada card declined. The fields stand before the message; a debug entry ends with its file:line (source: LogSource.Debug, Always or Never). The level takes its color (WARN and ERROR in the terminal theme's warning and failure styles, FATAL white on red), the message of an error is bold, the field values are cyan, the rest is dimmed. Colors are left out when the stream is not a terminal or NO_COLOR is set, and forced by FORCE_COLOR.
JSONEncoder(fieldsKey = "", source = true)One JSON object: time (RFC 3339, UTC), level, message, module, file, line, trace and span for an entry written inside a Telemetry span, then the fields. With fieldsKey the fields go under that key.
syslog.EncoderRFC 5424 or RFC 3164 messages (std.net.syslog).
journald.EncoderJournal entries (std.proto.journald): MESSAGE, PRIORITY, the call site and each field under an upper-case name.
WriterSends records to
ConsoleWriter(stream = ConsoleStream.Stderr)Standard error or standard output, a line each.
FileWriter(path, rotation = LogRotation{})A file, a line each. LogRotation starts a new file at maxSize bytes or every period (counted from midnight, moved by offset), keeps the newest keep rotated files and those younger than maxAge, and gzips them with compress. A rotated file is named app.log.20261012T140509Z. rotate() starts a new file now.
NetworkWriter(address, transport = LogTransport.Tcp)A collector at host:port: over TCP or TLS (LogTransport.Tls, the collector's certificate verified; tls takes a tls.Config) a line per record on one connection, over UDP a datagram per record. While the collector cannot be reached, records fail at once for retryAfter.
syslog.ClientA syslog daemon or collector over UDP, TCP, TLS or the local socket.
journald.ClientThe systemd journal, with journald.Encoder: the record's fields become journal fields (journalctl ORDER_ID=A-17).

A writer of your own is any io.WriteCloser: each write receives one whole encoded record. An encoder is any type with def encode(record LogRecord) !byte[]. A value that implements LogValue (def logValue() any) is logged as what that returns.

  • commitdef debug(message string, fields map<string, any> = {})

    Writes a log entry at debug level.

  • commitdef info(message string, fields map<string, any> = {})

    Writes a log entry at info level.

  • commitdef warning(message string, fields map<string, any> = {})

    Writes a log entry at warning level.

  • commitdef error(message string, fields map<string, any> = {})

    Writes a log entry at error level.

  • querydef fatal(message string, fields map<string, any> = {}, wait duration = 5s)

    Writes a log entry at fatal level and waits up to wait for the writers to have it, with everything written before it. The program goes on: ending it is the caller's decision.

  • commitdef log(level LogLevel, message string, fields map<string, any> = {})

    Writes a log entry at the given level.

  • commitdef log(message string, fields map<string, any> = {})

    Writes a plain log entry, one without a level: it is written whatever the configured level is, every writer gets it, and the encoders write LOG where the level stands.

  • commitdef write(record LogRecord)

    Writes a record built by the caller, for code that passes on entries made elsewhere: the level, message, fields, call site and trace are taken as they are, and the time too unless it is 0, which stands for now.

  • commitdef context(scope string, fields map<string, any>)

    Attaches fields to every entry written from scope, the qualified name of a function (billing.charge), a method (billing.Till.ring) or a type (billing.Till, for all its methods): what @logContext does. A method's fields win over its type's, and a field passed to the write call itself over both. Empty fields take the scope's away.

  • querydef flush(timeout duration = 5s) !none

    Waits until every record written so far has been handed to the writers.

    Fails when that takes longer than timeout.

  • querydef enabled(level LogLevel) bool

    Reports whether an entry at level would be written, for code that builds costly fields only when they are used.

  • querydef level() LogLevel

    The lowest level that is written.

  • commitdef setLevel(level LogLevel)

    Sets the lowest level that is written.

    Entries below it are dropped before they reach any writer.

  • querydef dropped() int

    How many records were dropped because the queue was full (LogConfig.capacity, 8192) or the service stopped.

  • emitdef recorded(record LogRecord) LogRecord

    Emitted for each record the service accepted, as the writers get it: past the level filter, with its time and call site, the @logContext fields merged in and the LogValue and @sensitive values replaced. It is emitted in the order the records were written, before the record goes to the writers.

  • emitdef writeFailed(failure LogWriteFailure) LogWriteFailure

    Emitted for each record a writer did not take, because its encoder or the writer failed. The first failure of a writer is also reported on standard error.

Messaging

In-memory queues of messages by topic, with consumer groups, priorities, delays, retries and dead letters.

A message is published on a topic and goes to every subscription whose topic matches. A subscription names one topic or a pattern: * matches any run of characters and ? one, so orders.* takes orders.created and orders.eu.paid. Subscriptions with the same topic and group share the messages, each going to one of them; a subscription without a group gets its own copy. A message nobody subscribes to is dropped.

A handler that fails gets the message again after retryDelay, doubled each time up to maxRetryDelay. After maxAttempts deliveries the message is published as a DeadLetter on the topic messaging.dead. Messages live in memory: stopping the service drops them and every subscription.

messaging.adm
use std.services.messaging::(Messaging, Message, subscribe, topic)

@topic("orders.created")
struct OrderCreated {
	id int
}

@subscribe()
def confirm(order OrderCreated) !none {
	try mail.send(order.id)
}

@subscribe("orders.*", group = "audit")
def audit(message Message) {
	println(message.topic)
}

try Messaging.publish(OrderCreated{id: 7})
try Messaging.publish("hello", "greetings", priority = 5, delay = 2s)
MessagingConfig.maxAttempts int = 5 How many times a message is delivered to one subscription before it becomes a dead letter.
MessagingConfig.retryDelay duration = 100ms The wait before the second delivery; it doubles before each later one.
MessagingConfig.maxRetryDelay duration = 30s The longest wait between two deliveries.
MessagingConfig.capacity int = 0 How many messages may wait on one subscription; 0 for no limit.

A Message carries id, topic, payload any, headers, priority, time, attempt (1 on the first delivery) and subscription. Calls fail with a MessagingError whose kind is Stopped, InvalidTopic, Full, UnknownSubscription, NotPending, InvalidHandler or InvalidConfig.

  • commitdef config(val MessagingConfig) !none

    Applies the settings; messages already waiting keep the attempts they have had.

  • commitdef publish(payload any, topic string = "", priority int = 0, delay duration = 0s, headers map<string, string> = {}) !MessageId

    Publishes payload and returns the message's id.

    Without a topic the message goes to the topic of the payload's type: the name its @topic gives, else the type's qualified name. Higher priorities are delivered first; delay keeps the message back for that long.

    Fails when the topic holds *, ? or \, when no topic is given and the payload is not a struct, a type or an enum, and when a subscription the message goes to is full.

  • commitdef subscribe(topic string, handler def (Message) !none, group string = "", workers int = 1) !SubscriptionId

    Subscribes handler to topic and returns the subscription's id.

    The handler runs on workers tasks of its own, one message at a time on each. A message it fails on is delivered again.

  • commitdef subscribe(topic string, group string = "") !SubscriptionId

    Subscribes without a handler: the messages wait until receive takes them, and each is settled with ack or nack.

  • commitdef attach(consumer any) !SubscriptionId[]

    Subscribes every @subscribe method of consumer's type, each run on consumer, and returns their subscriptions.

  • commitdef unsubscribe(id SubscriptionId) !none

    Ends the subscription. A receive waiting on it returns none; messages it took and did not settle go back to its group.

  • querydef receive(id SubscriptionId, timeout ?duration = none) !?Message

    Returns the next message of a subscription made without a handler, waiting up to timeout: forever when none, not at all when 0s.

    Returns none when the time is up and when the subscription or the service ends.

  • commitdef ack(message Message) !none

    Settles a received message: it was handled.

  • commitdef nack(message Message, delay ?duration = none) !none

    Settles a received message: it was not handled. It is delivered again after delay, or becomes a dead letter when that was its last attempt.

  • querydef subscriptions() Subscription[]

    Every subscription with its topic, group, payload type and counts: pending, in flight, delivered, failed.

Network

Sockets, addressing, name resolution, and socket options.

Lab conditions (Network.lab.conditions, see Service labs). The defaults change nothing.

unreachable string Endpoints that cannot be reached, as a glob ("*.example.com:443", "*" for no network): connecting fails with EHOSTUNREACH.
refused string Endpoints nobody listens on, as a glob: ECONNREFUSED.
unresolved string Host names that do not resolve, as a glob.
hosts map<string, string[]> The addresses of host names, asked before any resolver: send the program to a server of the test's own.
bytesPerSecond int How fast reads and writes move data.
chunk int The most bytes one read or write moves.
breaksAfter ?int64 The bytes that still pass before the connections break with ECONNRESET.
datagramLoss float The share of datagrams sent that never arrive.
silent bool Nothing arrives: a read waits its timeout out and fails with ETIMEDOUT.
network_lab.adm
Network.lab.conditions.hosts = map<string, string[]>{"api.example.com": ["127.0.0.1"]}
Network.lab.conditions.bytesPerSecond = 32000    // a slow mobile link
Network.lab.conditions.breaksAfter = 4096        // drops mid-download
Network.lab.conditions.unreachable = "*"         // offline
  • commitdef socket(domain net.SocketDomain, typ net.SocketType) !net.Socket

    Opens a socket for the domain and type; the protocol follows from them. The socket owns its descriptor and must be closed. Errors carry the errno as `error.code` (`std.net.EAddrInUse`) and name the failing operation.

  • commitdef socketFd(domain net.SocketDomain, typ net.SocketType) !int

    Opens a socket and returns its raw descriptor, for code that works with descriptors rather than `std.net.Socket`.

  • querydef bind(fd int, addr Endpoint) !none

    Binds a socket to a local address: `host:port` or `:port` for IPv4, `[host]:port` or `[]:port` for IPv6, an absolute path for a Unix socket. An empty host binds every interface, port 0 picks a free one (`localAddr` tells which). An existing Unix socket path is not removed.

  • querydef connect(fd int, addr Endpoint, timeout duration = 30s) !none

    Connects a socket to a remote endpoint, waiting at most `timeout` for the handshake; a timeout fails with `ETIMEDOUT` and leaves the socket open for the caller to close or retry.

  • querydef accept(fd int) !int

    Accept a new connection from a listening stream socket FD.

    Returns the accepted connection as a new file descriptor; the caller owns the returned FD and must close it.

  • commitdef close(fd int) !none

    Close a socket FD.

    Closing an already-closed FD is an OS error; `std.net` wrappers typically expose `close()` and forward to this method.

  • commitdef listen(fd int, backlog int) !none

    Mark a bound stream socket as a listener.

    `backlog` is passed through to the OS; its effective value may be clamped by the platform.

  • querydef write(fd int, data byte[], timeout duration = 30s) !int

    Write bytes to a connected stream socket FD.

    Returns the number of bytes written; short writes are normal and callers must loop if they need to send the full buffer (see `std.net.Connection.writeAll`).

    `timeout` bounds the wait for the FD to become writable, not an end-to-end "flush to peer" guarantee.

  • querydef sendUrgent(fd int, value byte) !none

    Sends one byte of TCP urgent (out-of-band) data on a stream socket FD; the peer's socket marks the point in the stream it sits at.

    Fails when fd is closed or the peer is gone.

  • querydef urgentPending(fd int) !bool

    Reports whether urgent data waits to be read on a stream socket FD, without blocking.

    Fails when fd is not a socket.

  • querydef urgentInline(fd int, enabled bool) !none

    Makes urgent data arrive inline on a stream socket FD, in its place among the other bytes, instead of apart.

    Fails when fd is not a socket.

  • querydef read(fd int, data byte[], timeout duration = 30s) !int

    Read bytes from a connected stream socket FD.

    Returns the number of bytes read (0 indicates EOF / peer shutdown).

    `timeout` bounds the wait for the FD to become readable.

  • querydef resolve(name string) !string[]

    Resolve a DNS name to IP literals (IPv4 and/or IPv6).

    The returned strings never include ports. For "host:port" parsing and socket endpoint construction, see `std.net.Address`.

  • querydef resolverConfig() string

    The system's resolver configuration in the resolv.conf format (/etc/resolv.conf; on Windows the DNS servers of the adapters that are up and the search list), "" when there is none. Read without a storage permission: name resolution is part of connecting.

  • querydef hostsFile() string

    The text of the system's hosts file (/etc/hosts; on Windows System32\drivers\etc\hosts), "" when it cannot be read. Read without a storage permission, as resolverConfig is.

  • querydef sendToNameServer(fd int, query byte[], server string, timeout duration = 5s) !int

    Sends a DNS query to one of the system's name servers from a datagram socket. The server is not checked against the connect policy, since resolving a name is part of connecting to it; the names the query asks about are, as hosts.

    Fails when server is not one of dns.systemConfig().servers, the bytes are not a DNS query, the policy denies one of its names, or the send is refused or times out.

  • querydef exchangeWithNameServer(query byte[], server string, timeout duration = 5s) !byte[]

    Sends a DNS query to one of the system's name servers over TCP and returns the response, checked as sendToNameServer checks.

    Fails as sendToNameServer does, and when the connection fails, times out or closes before a whole response arrived.

  • querydef dialStream(addr Endpoint, timeout duration = 30s) !int

    Resolve + connect a stream socket and return a connected file descriptor.

    This is the lowest-level "dial" helper: it returns a raw FD so higher-level wrappers (`std.net.dialStream`, connection pools, etc.) can build on it.

    A TCP socket comes back sending what it is given at once (TCP_NODELAY), as one from connect or accept does; setNoDelay(fd, false) changes that.

  • querydef sendTo(fd int, data byte[], addr Endpoint, timeout duration = 30s) !int

    Send a datagram to a remote address (UDP / Unix datagram).

    Returns the number of bytes sent. For datagrams this is usually either `data.len()` or an error.

  • querydef send(fd int, data byte[], timeout duration = 30s) !int

    Sends one datagram on a datagram socket connected to its peer (connect): no address travels with the call, and the policy was asked when the socket was connected.

    Returns the number of bytes sent. Fails when fd is closed, not connected, or the send is refused or times out.

  • querydef receive(fd int, data byte[], timeout duration = 30s) !int

    Receives the next datagram from the peer a datagram socket is connected to and returns its length; one longer than data is cut to it.

    Fails when fd is closed or the timeout passes.

  • querydef recvFrom(fd int, data byte[], timeout duration = 30s) !(int, string)

    Receive a datagram and return the byte count and sender address string.

    The returned address uses the same string format accepted by `sendTo` and `std.net.Address.parse`.

  • querydef sendDescriptor(fd int, descriptor int, addr string = "", timeout duration = 30s) !none

    Hands an open descriptor (a file, a memory file, a socket) to the process at the other end of a Unix socket, which gets a descriptor of its own for the same open file; this one stays open. addr is the path of the receiving socket for a datagram socket that is not connected. On a stream socket one zero byte travels with the descriptor, which receiveDescriptor takes off again.

    Fails when fd is not a Unix socket, the policy denies addr, the peer is gone or its queue stays full for the timeout.

  • querydef receiveDescriptor(fd int, timeout duration = 30s) !int

    Takes the next message of a Unix socket and returns the descriptor it carries, which the caller owns and must close.

    Fails when fd is closed, the timeout passes, or the message carries no descriptor (ENOMSG).

  • commitdef shutdown(fd int, how int = 2) !none

    Shutdown one or both halves of a connection.

    `how` values: - 0: read half (further reads fail/EOF) - 1: write half (further writes fail) - 2: both (full shutdown)

  • commitdef setReuseAddr(fd int, enabled bool = true) !none

    Enable or disable SO_REUSEADDR.

    This is typically required for servers that need to restart quickly.

  • commitdef setBroadcast(fd int, enabled bool = true) !none

    Enable or disable SO_BROADCAST, which a datagram socket needs to send to a broadcast address (255.255.255.255, a subnet's last).

    Fails when the operating system rejects the option for this socket.

  • commitdef setNoDelay(fd int, enabled bool = true) !none

    Enable or disable TCP_NODELAY (TCP only).

    For non-TCP sockets this may fail with an OS error.

  • commitdef setKeepAlive(fd int, enabled bool = true) !none

    Enable or disable SO_KEEPALIVE.

    Keepalive behavior is OS-configured; this only toggles it.

  • commitdef setReusePort(fd int, enabled bool = true) !none

    Enable or disable SO_REUSEPORT when supported by the platform.

    Some OSes do not expose this option; in that case this returns an error.

  • commitdef setLinger(fd int, enabled bool, seconds int = 0) !none

    Control SO_LINGER.

    When enabled, `seconds` controls how long the OS may block during `close` in an attempt to deliver queued data.

  • commitdef setRecvBuf(fd int, bytes int) !none

    Set SO_RCVBUF.

  • commitdef setSendBuf(fd int, bytes int) !none

    Set SO_SNDBUF.

  • commitdef joinGroup(fd int, group string, iface string = "") !none

    Joins a multicast group on a datagram socket, so that what is sent to the group reaches it: 224.0.0.251, ff02::fb. iface names the interface by its address (IPv4) or its name (IPv6); empty leaves the choice to the system's routing.

    Fails when the address is no multicast group, the interface does not exist or has no multicast, or the system refuses.

  • commitdef leaveGroup(fd int, group string, iface string = "") !none

    Leaves a multicast group joined with joinGroup.

    Fails when the socket is no member of the group.

  • commitdef setMulticast(fd int, ttl int = 1, loop bool = true) !none

    Sets how many hops what the socket sends to a multicast group may travel (1 stays on the link, which is the system's default), and whether this host's own members of the group get it too.

    Fails when the operating system rejects the option for this socket.

  • commitdef setSockOptInt(fd int, level int, opt int, val int) !none

    Sets an integer socket option by its `setsockopt` level and name, for options `std.net.Socket` does not wrap.

  • commitdef getSockOptInt(fd int, level int, opt int) !int

    Read an integer socket option.

  • querydef localAddr(fd int) !string

    Return the local endpoint of a socket FD, formatted as an address string.

    For stream sockets, this is useful after binding `:0` to discover the chosen ephemeral port. For Unix sockets, this is typically the bound path.

  • querydef peerUserId(fd int) !int

    The user id of the process at the other end of a connected Unix socket, as the kernel recorded it when the connection was made: who is calling, without anything the caller sends. net.Connection.peerUserId() asks it for a connection.

    Fails when fd is not a connected Unix socket, or on a platform that does not report it (Windows).

  • querydef remoteAddr(fd int) !string

    Return the remote endpoint of a connected socket FD, formatted as an address string.

  • querydef publicAddr() !string

    Returns the host's public address as seen from outside the local network: the IP address a STUN server (RFC 8489) saw a Binding request come from. Behind a NAT that is the NAT's outside address. The servers of NetworkConfig.stunServers are asked in turn (Network.config(NetworkConfig{stunServers: ["stun.example.org:3478"]}); by default Google's and Cloudflare's public servers), each with a request sent three times, so a server that never answers costs 3.5 seconds.

    Fails when no STUN server is configured, the policy denies connecting to one (adm.network.connect), or none answers.

  • querydef netIface() !net.NetInterface[]

    Lists the host's network interfaces with their addresses and `std.net.NetIface*` flags.

  • querydef sockets() SocketInfo[]

    Returns runtime information for all sockets owned by the Network service: each one's descriptor, domain, type, when it was opened, and its local and remote address.

  • querydef socketInfo(fd int) ?SocketInfo

    Returns runtime information for the specified socket.

Notifications

Desktop notifications through the platform's notification server. On Linux this is the freedesktop server on the D-Bus session bus; on the web the browser's notifications, where the first show asks the user's permission, a click on a notification is its "default" action and notifications have no buttons; other platforms report nothing available yet. Showing one needs the adm.notifications.show permission.

  • commitdef show(notification Notification) !NotificationId

    Shows a notification and returns the id the server gave it. Notification carries the title, body, an icon name or absolute image path, the level (Info to Critical), action buttons, a timeout and an id to replace.

    Fails when the policy denies it, there is no notification server, or the server refuses it.

  • commitdef show(title string, body string = "") !NotificationId

    Shows a notification with a title and an optional body and returns its id.

  • commitdef close(id NotificationId) !none

    Takes a shown notification off the screen.

    Fails when there is no connection or the server does not know the id.

  • commitdef ask(notification Notification, wait duration = 60s) !string

    Shows a notification whose buttons are a question and waits for the answer: the pressed action's id, or "" when the user dismissed it or nobody answered within wait (the notification is then closed).

    Fails when there is no notification server or it refuses the notification.

  • querydef capabilities() NotificationCapabilities

    Reports what the notification server can do: actions, body text, markup, images, sound, persistence. Nothing is available when there is no server.

  • querydef shown() NotificationId[]

    The ids of the notifications shown by this program that the server has not closed yet.

  • emitdef action(event NotificationAction) NotificationAction

    Emitted when the user presses one of a notification's buttons; the event names the notification and the action id.

  • emitdef closed(id NotificationId) NotificationId

    Emitted when a notification leaves the screen: dismissed, expired or closed.

Policy

Permissions and policy decisions.

  • commitdef registerSpec(name string, factory PolicySpecFactory) !none

    Registers the spec type a permission names, so the file's rules for it can be checked against arguments.

  • commitdef registerResolver(handler PolicyResolver) !none

    Registers the resolver that answers `ask` rules. Without one an `ask` prompts through a desktop notification (Allow, Always allow, Deny); with prompts off (`ADM_POLICY_PROMPT=0`) it denies.

  • commitdef registerResources(permission string, patterns string[]) !none

    Names what std opens on its own account on the way to something the program did ask for: the system's random source behind a DNS query, the trusted certificates behind a TLS connection. The code that opens them registers them, as patterns for the constrained argument of the permission. An ask rule then never puts such a value to the user, who could not judge it; a deny rule still denies it.

    Fails with PolicyDenied when a package is on the call chain: a library cannot exempt what it opens from the user's questions.

  • commitdef configure(text string) !none

    Loads rules from policy-file text instead of the file, replacing what was loaded. Fails on an unknown permission, field or kind, or a malformed line, naming the line.

  • querydef requirePermissions(permissions string[]) !none

    Fails unless every package on the call chain holds each permission.

  • commitdef revokePermissions(permissions string[]) !none

    Drops permissions from the application's own grants for the rest of the run.

  • querydef hasPermissions(permissions string[]) bool

    Whether every package on the call chain holds each permission.

  • commitdef register(registration PolicyRegistration) !none

    Registers an owner's rules and validators for one action.

  • querydef evaluate(request PolicyRequest) !PolicyDecision

    Decides a request and reports why: which package lacks the permission, or the merged rules that apply.

  • querydef explain(request PolicyRequest) !PolicyDecision

    Explains a request: the decision, the rule and layer behind it.

  • querydef require(request PolicyRequest) !none

    Evaluates the specified policy request against the compiled grant table for every package on the current call chain, the application first.

    Returns successfully when allowed. When denied, fails with a `PolicyDenied` naming the permission and the package lacking it.

  • querydef text() string

    The policy file as it would be written from the loaded rules and settings: one table per subject and permission, the comment preamble kept.

  • querydef validate(permission string, spec string, value string) !none

    Checks one constrained argument of a gated function against the merged rule for its permission: the file's rules for the program and for every package on the chain, then the registered spec's own check. An `ask` outcome goes to the resolver, else to a desktop notification the user answers; unanswered denies. The policy state itself is denied to every subject.

  • querydef list() Permission[]

    Lists every permission the program's services declare.

  • emitdef decided(entry PolicyLogEntry) PolicyLogEntry

    Emitted for every denial, ask and live answer.

  • querydef promptText(permission string, value string) string

    What the user reads under the prompt's title: what the permission lets a program do, in the words its service declared, then the file, folder or address it is about to be used on, then the package asking when that is not the application itself. Permission names stay out of it, they are for policy files; one shows only when its service gave no description. A resolver that draws its own dialog can show the same words.

Runtime

The ADM runtime itself - host info, memory, environment, service registry, and the one door to other programs: run starts a command through the system shell behind the adm.runtime.spawn permission, whose description says what granting it means: trusting the package completely. Foreign code (@c, @link, @native, @extern) is not a permission: a dependency binding it is accepted by the developer at install and refused by the build otherwise, see Policy.

  • commitdef open(address string) !none

    Opens an address in the program the desktop chooses for it: a web page in the browser, a mailto: address in the mail program, a file in its viewer. The desktop's opener gets the address as its one argument, with no shell in between. Needs adm.runtime.open, which is narrower than adm.runtime.spawn. Fails when the policy denies it, the address is empty or starts with -, or the opener cannot open it.

  • querydef systemInfo() SystemInfo

    Returns information about the host system running the ADM runtime.

  • querydef workingDir() string

    Returns current process working directory.

  • querydef memoryUsage() MemoryUsage

    Returns memory usage statistics for the current ADM runtime.

  • querydef cpuUsage() metrics.Histogram

    The program's processor use: one sample a second since the program started, each the share of the machine's processor time (all its logical CPUs) the program used in that second, in percent, so 100 is every CPU busy. It holds the last minute; cpuUsage().last() is the latest second. On the web, which does not report processor time, every sample is 0.

  • querydef sizeOf(val any) int

    Returns the value's deep retained footprint in bytes. Runtime object and allocation headers, collection capacity, and recursively retained values are included; shared allocations are counted once. UTF-8 string storage is counted even for literals, while unrelated static/external storage is not.

  • querydef getEnv(key string) ?string

    Returns value of the specified environment variable.

    Returns none when the variable is not defined.

  • querydef environment() map<string, string>

    Returns every environment variable of the process as it is now, by name.

  • querydef services() ServiceInfo[]

    Returns runtime information for all registered services.

  • querydef serviceInfo(name service) ?ServiceInfo

    Returns runtime information for the specified service.

    Returns none if the service is not registered with the runtime.

  • querydef loadPlugin(path string) ?Plugin

    Reads the metadata of the plugin package at the path: its name, version, publisher and what it publishes. No code of the plugin is loaded or run. A package whose signature or checksums do not verify is returned without exports, and every call on it fails with the reason. Returns none when the path is not a readable plugin package. See Plugins.

  • querydef loadPlugins(folder string) Plugin[]

    Reads the metadata of every plugin package (*.admplugin) in the folder, in file name order, loading no code. A file that is not a readable package is left out; a folder that cannot be listed gives an empty list.

  • commitdef run(command string, input string = "") !ProcessResult

    Runs a command through the system shell with input as its standard input and returns what it printed and its exit code (128 + signal when killed, 127 when the shell could not start it). Needs adm.runtime.spawn. std.os.run and std.os.output wrap it.

    Fails when the policy denies it or the process could not be started, which the OS sandbox causes; a command that exited with an error is reported through the code.

  • commitdef start(program string, args string[] = [], env map<string, string> = {}, dir string = "", errors bool = false) !os.Process

    Starts a program and returns while it runs. program is a path, or a name looked up in PATH; args follow it as written, with no shell in between. env adds to or replaces variables of this program's environment for the started one, and dir is its working directory. The result's input and output pipes are the program's standard input and output; its standard error is this program's, or the pipe errors when errors is true. wait() returns the exit code (128 + signal when a signal ended it), wait(timeout) none while it still runs, terminate() asks it to stop and kill() stops it. Needs adm.runtime.spawn.

    Fails when the policy denies it or the program could not be started: no such program, no right to run it, no such folder.

  • commitdef setEnv(key string, value string) !none

    Sets value of the specified environment variable for the current process.

  • emitdef serviceStatusChanged(change ServiceStatusChange) ServiceStatusChange

    Emitted when runtime status of a registered service changes.

Scheduler

Cron, one-shot, and repeating schedules.

  • commitdef cron(cronExpression string, name string = "") !ScheduleId

    Adds a schedule that fires on the cron expression and returns its id.

  • commitdef runAt(timestamp time.Time, name string = "") !ScheduleId

    Adds a schedule that fires once at the given time and returns its id.

    The time must be in the future.

  • commitdef runAfter(dur duration, name string = "") !ScheduleId

    Adds a schedule that fires once after the duration and returns its id.

    The duration must be positive.

  • commitdef runEvery(interval duration, name string = "") !ScheduleId

    Adds a schedule that fires every interval and returns its id.

    The interval must be positive.

  • commitdef cancel(id ScheduleId) !none

    Removes the schedule.

    Fails when there is no such schedule.

  • commitdef pause(id ScheduleId) !none

    Pauses the schedule.

    Its timing is untouched; firings that fall due while it is paused are skipped.

    Fails when there is no such schedule.

  • commitdef resume(id ScheduleId) !none

    Resumes a paused schedule.

    It carries on with its original timing.

    Firings skipped while it was paused are not made up.

    Fails when there is no such schedule.

  • querydef list() Schedule[]

    Every schedule.

  • querydef get(id ScheduleId) ?Schedule

    The schedule with that id, or none.

  • querydef next(id ScheduleId) ?time.Time

    When the schedule next fires; none when there is no such schedule or it is paused.

  • emitdef trigger(id ScheduleId) ScheduleId

    Emitted when a schedule becomes due.

    The service only keeps time; an observer does the work.

Storage

Files, directories, memory maps, and device control. A Path argument takes a plain string; the policy checks it before the method runs.

A failed call names the operation in its message (open: No such file or directory) and carries the errno's name as error.code on every system: ENOENT, EEXIST, EACCES, EBUSY, ENOTEMPTY, ... The lab's failures carry the same codes, so a handler tested under the lab works on a real disk.

let text = os.readFile(path) onerror (err error) {
	recover [] when err.code == "ENOENT"   // first run: nothing saved yet
	fail err
}

Lab conditions (Storage.lab.conditions, see Service labs). The defaults change nothing.

readOnly bool Every change fails as on a read-only file system (EROFS).
freeSpace ?int64 The bytes the disk still has: writes use them up and fail with ENOSPC when one does not fit; freeSpace answers this number.
chunk int The most bytes one read or write moves.
corruptReads float The share of reads that come back with one bit flipped.
missing string Paths that look absent, as a glob over the whole path: ENOENT, and exists says no.
bytesPerSecond int How fast reads and writes move data.
storage_lab.adm
Storage.lab.conditions.freeSpace = 4096             // the disk fills up
Storage.lab.conditions.readOnly = true
Storage.lab.conditions.missing = "*/settings.json"  // first run again
Storage.lab.conditions.corruptReads = 0.01
  • commitdef open(path Path, mode string) !int

    Open a file or folder at the given path with the specified mode.

  • emitdef fileOpened(path string) string

    Emitted after a file is opened.

  • commitdef close(fd int) !none

    Closes an open file handle.

  • commitdef write(fd int, data byte[]) !int

    Writes bytes at the handle's current position and returns how many were written.

  • commitdef read(fd int, data byte[]) !int

    Reads into the buffer from the handle's current position and returns how many bytes were read.

  • commitdef readFile(path Path) !byte[]

    Reads a whole file into memory.

  • commitdef writeFile(path Path, data byte[]) !none

    Writes the bytes to a file, replacing any existing content.

  • commitdef seek(fd int, offset int64, whence int) !int64

    Moves the handle's position and returns the new absolute offset.

    Whence follows the Whence enum: start, current, or end.

  • commitdef readAt(fd int, data byte[], offset int64) !int

    Reads into the buffer starting at an absolute offset, leaving the handle's position unchanged.

  • commitdef writeAt(fd int, data byte[], offset int64) !int

    Writes bytes at an absolute offset, leaving the handle's position unchanged.

  • commitdef truncate(fd int, size int64) !none

    Resizes the file to the given size, padding with zero bytes when it grows.

  • querydef sync(fd int, dataOnly bool = false) !none

    Pushes what was written to the file through to the storage device, so it survives a crash or a power cut. With dataOnly the content alone, without the file's times.

  • querydef lock(fd int, exclusive bool = true, wait bool = true) !bool

    Takes an advisory lock on the whole file: exclusive, or shared with other shared holders. With wait it returns once the lock is held; without, false when someone else holds it.

  • querydef unlock(fd int) !none

    Gives up the lock lock took.

  • commitdef memoryFile(name string = "") !int

    Opens a file that lives in memory and has no path: it is read, written, mapped and closed as any open file, and is gone when its last descriptor closes. name only labels it for someone inspecting the process. No path permission applies.

    Fails when the operating system has no memory files (only Linux has today) or no descriptors left.

  • commitdef seal(fd int) !none

    Makes a memory file final: its bytes and its size cannot change again, through this descriptor or any other, and that cannot be undone.

    Fails when fd is not a memory file or part of it is mapped writable.

  • commitdef mmap(fd int, offset int = 0, length int = 0, access MapAccess = MapAccess.Read) !int

    Maps length bytes of the open descriptor from offset into memory and returns a mapping handle; length 0 maps to the end of the file.

    A device node reports no size, so a caller mapping one passes the length itself.

    Fails when the descriptor cannot be mapped.

  • commitdef mmapRead(handle int, offset int, length int) !byte[]

    The mapped bytes from offset.

    Fails when the range leaves the mapping.

  • commitdef mmapWrite(handle int, offset int, data byte[]) !none

    Writes into the mapping at offset.

    Fails on a read-only mapping or a range that leaves it.

  • querydef mmapLength(handle int) int

    Bytes in the mapping, 0 for an unknown handle.

  • commitdef mmapFlush(handle int) !none

    Pushes a shared mapping's changes to the file.

    Fails when the handle is not a mapping.

  • commitdef munmap(handle int) !none

    Releases the mapping.

    Fails when the handle is not a mapping.

  • commitdef control(fd int, request int, data byte[]) !int

    Sends a driver request on the descriptor with data as its argument, which the driver writes its answer back into, and returns the request's result: 0 for most requests, a count for some (a feature report's length, a name's length, a new descriptor).

    Fails when the driver refuses.

  • commitdef control(fd int, request int, value int) !int

    Sends a driver request whose argument is a number rather than a buffer (grabbing an input device, removing a force-feedback effect, flushing a terminal), and returns the request's result.

    Fails when the driver refuses.

  • querydef wait(fd int, timeout duration, write bool = false) !bool

    Waits until the descriptor is readable, or writable when write is true, or the timeout passes; true when it is ready.

    A query, not a commit: the wait must not hold the service's lock while it lasts.

    Fails when the descriptor cannot be waited on.

  • commitdef exists(path Path) !bool

    Returns whether a file or directory exists at the path.

  • commitdef remove(path Path) !none

    Removes a file.

  • commitdef rename(oldPath Path, newPath Path) !none

    Renames or moves a file or directory.

  • commitdef mkdir(path Path) !none

    Creates a directory, including any missing parents.

  • commitdef rmdir(path Path) !none

    Removes an empty directory.

  • querydef readDir(path Path, depth int = 0) !os.DirEntry[]

    Lists the entries of a directory.

    Depth 0 lists the directory itself; a larger depth descends that many levels.

  • commitdef delete(path Path) !none

    Delete a file or a folder at the given path

  • querydef stat(path Path) !os.FileInfo

    Returns size, mode, timestamps and the file system's identity numbers (device, inode, owner, group, link count) for a path.

  • querydef homeDir() !string

    Returns the user's home folder: HOME, or USERPROFILE on Windows. os.homeDir() is the same call.

    Fails when the environment names none.

  • querydef resolve(path string) !string

    Returns the absolute path a path names, in its shortest form: ~ and ~/... start at the user's home folder, a relative path at the working directory, and a package path is the file the package shipped. os.resolve(path) is the same call.

    Fails when the path starts with ~ and no home folder is known, or names a package without shipped files.

  • querydef readLink(path Path) !string

    Returns the target a symbolic link holds, as written in the link: a relative target is relative to the link's folder and is not resolved.

    Fails when the policy denies the path, the path is not a link or the operating system refuses.

  • querydef openedFiles() OpenedFile[]

    Returns the files the Storage service opened (open, memoryFile) and has not closed, ordered by descriptor, each with its path, mode, the time it was opened and its reads and writes so far. A descriptor that came from elsewhere (Network.receiveDescriptor) is not listed.

  • commitdef chmod(file Path, mode int64) !none

    Changes a file's permission bits.

  • commitdef touch(file Path, modified time.Time) !none

    Sets a file's modification time; the access time is left as it is.

    Fails when the policy denies the path or the operating system refuses.

  • querydef metrics(fd ?int) !FileMetrics

    Returns the reads and writes (reads, writes, bytesRead, bytesWritten) of the open file fd, or with fd none those of every file since the service started: closed files, readFile, writeFile and mapped reads and writes included.

    Fails when fd is not a file the Storage service has open.

  • querydef freeSpace(disk Path) !int64

    The bytes still free for the program on the disk that holds the path (the file system it is on).

    Fails when the policy denies the path or the operating system refuses.

  • querydef totalSpace(disk Path) !int64

    The size in bytes of the disk that holds the path.

    Fails when the policy denies the path or the operating system refuses.

  • querydef disks() !Disk[]

    Returns the mounted file systems that hold files: disks, partitions, network shares, each with its device, mount point, file system type and whether it is read-only or on another machine. Pseudo file systems (proc, sysfs, tmpfs, overlay, ...) and package images (squashfs: snaps, AppImages) are left out. Their sizes are freeSpace and totalSpace of the mount point, which the read policy decides on.

    Fails when the operating system cannot list its mounts.

Telemetry

Sends what the program knows about itself to a server its developer configures: metrics, log records, errors, what the user did, and timed operations.

Nothing is collected until the service is configured with an exporter and started, and, unless requireConsent is off, until the user has agreed. The answer is kept in the Vault, the batches a collector did not take in the application's home folder; what is sent carries the vault's id (Vault.id()) as app.installation.id.

telemetry.adm
use std.services.telemetry::(Telemetry, TelemetryConfig, OTLPExporter, span)

try Telemetry.config(TelemetryConfig{
	exporters: [new OTLPExporter("https://telemetry.example.com:4318")],
})
try await Telemetry.start()

try Telemetry.consent(true)                              // the user said yes
Telemetry.event("export.finished", {"format": "pdf"})    // what the user did
Telemetry.report(err, {"order": order.id})               // an error
Telemetry.set("experiment.checkout", "B")                // on everything sent from now on

let work = Telemetry.span("import", {"rows": rows.len()})
defer work.end()
let step = Telemetry.span("parse")                       // a child of work
step.end()
Metrics Everything the Diagnostics service holds, read every interval, with each metric's labels, unit and description.
Logs Every record the Logging service accepts, with the trace and span it was written in.
Errors Errors given to report, with the span they were reported in, and every Logging.fatal.
Events What the user did, as given to event.
Spans Timed operations: @span functions and Telemetry.span. From when it begins until it ends, a span is its task's current one: spans begun meanwhile, there or in tasks started with async, are its children. End a span on the task that began it, and the spans begun inside it first.
TelemetryConfig.exporters Exporter[] Where batches go: OTLPExporter (an OpenTelemetry collector, over HTTP as Protocol Buffers or JSON, or over gRPC), FileExporter (one JSON line per batch), ConsoleExporter (standard error), or any type with name() string, export(batch) !none and close() !none. The name is the same on every run: batches kept for an exporter return to the exporter of that name, and two of one name are refused. An OTLPExporter takes protocol, headers, timeout, compress and tls (roots and a client certificate; the system's roots without it).
TelemetryConfig.signals TelemetrySignal[] What is sent; all five by default.
TelemetryConfig.requireConsent bool = true Whether the user has to agree first. A program on its owner's machines, a server, sets it to false.
TelemetryConfig.name, version string The program as the collector sees it; the application's own when empty.
TelemetryConfig.resource map<string, string> More about the program, added to every batch.
TelemetryConfig.interval duration = 10s How often metrics are read and waiting records are sent.
TelemetryConfig.batch int = 512 A batch is also sent as soon as that many records wait.
TelemetryConfig.capacity int = 8192 How many records may wait in memory; one more is dropped and counted.
TelemetryConfig.sampling float = 1.0 The share of traces recorded.
TelemetryConfig.spool bool = true, spoolSize int = 16777216 Whether batches a collector did not take are kept on disk and sent later, also after a restart, and how many bytes they may take.
TelemetryConfig.scrub ?def (TelemetryBatch) TelemetryBatch Called with each batch before any exporter sees it; what it returns is what is sent.
  • commitdef config(val TelemetryConfig) !none

    Applies the settings. Needs the permission adm.telemetry.send.

  • commitdef consent(granted bool) !none

    Records the user's answer and keeps it across runs, in the Vault (telemetry.consent). From then on what is sent carries the installation id. Withdrawing drops what waits, in memory and on disk; the id stays in the vault, where other services rely on it, and is no longer sent.

  • querydef consented() ?bool

    The user's answer: none when they were never asked.

  • querydef installation() string

    The id sent with every batch: the vault's id (Vault.id()), which names the installation, not the user or the machine. Empty while the user has not agreed, unless requireConsent is off.

  • querydef collecting(signal TelemetrySignal = TelemetrySignal.Events) bool

    Whether records of the signal are collected now: the service runs with an exporter, the signal is configured, and the user agreed unless requireConsent is off.

  • querydef session() string

    A random id made each time the service starts.

  • commitdef set(key string, value string)

    Adds a value to everything sent from now on; an empty value takes the key away.

  • commitdef report(err error, fields map<string, any> = {})

    Sends an error with where it was raised, and the calling task's current span.

  • commitdef event(name string, properties map<string, any> = {})

    Sends something the user did.

  • querydef span(name string, attributes map<string, any> = {}, parent ?SpanContext = none) Span

    Begins a timed operation and makes it the calling task's current span; end on the result finishes it. The span is a child of parent, and without one of the task's current span. A Span has set, event, error, end, context and traceparent. A parent read by parseTraceparent is another program's (SpanContext.remote), and the span begun under it records that as SpanRecord.parentRemote.

  • querydef current() ?Span

    The calling task's current span: the last one begun on it, or inherited from the task that started it, that has not ended. None outside any span.

  • commitdef record(span SpanRecord)

    Sends a finished span built by the caller.

  • querydef flush(timeout duration = 5s) !none

    Sends what waits and returns once every exporter took it.

  • querydef dropped() int

    How many records were lost: written while capacity waited already, refused for good by a receiver, in a batch nothing could keep, or in a kept batch removed to stay within spoolSize.

An exporter that fails has the whole batch kept for another try. One that fails with an ExportFailure says more: rest is what to try again, rejected how many records the receiver refused for good, retryAfter a pause during which the exporter is left alone. OTLPExporter does: what the collector took is not sent again, a busy or absent collector gets the rest later and after the pause it asked for (Retry-After, gRPC RetryInfo, at most an hour), and records it refuses (any other answer, the count of a partial success) are dropped and counted. Each failure is emitted as Telemetry.exportFailed.

Lab conditions (Telemetry.lab.conditions, see Service labs). The defaults change nothing.

unreachable bool No exporter takes a batch, as if no receiver could be reached.
lossRate float The share of batches an exporter does not take.
sendDelay duration How much longer handing a batch to an exporter takes.
spoolFails bool Kept batches cannot be written to disk; they wait in memory instead.

UI

Windows, the renderer, displays and input events. Lives in std.ui: use std.ui::(UI).

  • querydef openWindow<T: component>(root T, cfg r.WindowConfig) !r.Window

    Opens a window that shows root: the component that stands for the window. Call it again for each further window; run serves them all. With cfg.persist = "main" the window opens with the size, place and state (maximized, fullscreen) it had when it was last open, on a display that still shows that place, and keeps them as the user changes them; the name is a key of the @persist store (Vault, persist.<name>). A second window opened under a name an open window already keeps opens as written and is not kept. From any task: while run serves the windows, a call from another task is made on the windows' loop at its next pass, and returns once it ran there.

  • querydef openDefaultWindow<T: component>(root T) !r.Window

    Opens a window with the `UIConfig.defaultWindow` settings and root as the component it renders.

  • querydef closeWindow(win r.Window) !none

    Closes the window. From any task, as openWindow.

  • querydef window(title string = "", name string = "") ?r.Window

    The open window with that title, or that was opened with that `WindowConfig.name`; with both, the one that has both. The first opened when several fit; none when none does.

  • querydef raiseWindow(win r.Window) !none

    Puts a window above the others. From any task, as openWindow.

  • querydef setWindowTitle(win r.Window, title string) !none

    Sets a window's title. While a Page shown in it declares a title, the page's is shown and this one comes back when no page declares one any more. From any task, as openWindow.

  • querydef setPageTitle(win r.Window, page component, title ?string) !none

    Declares the title of page, shown in win, in place of the window's own; none takes it back. The title of the page that declared last is shown: the innermost of pages that hold others. What Page calls. From any task, as openWindow.

  • querydef setWindowIcon(win r.Window, icon image.Image) !none

    Sets a window's icon. From any task, as openWindow.

  • querydef windows() r.Window[]

    The open windows.

  • commitdef pollPlatformEvents(win r.Window) !r.PlatformEvent[]

    Reads the raw platform events waiting on the window. Nothing is emitted; pumpEvents turns them into `std.ui` events. Events of other windows stay waiting for their own call.

  • commitasync def run<T: component>(root T, cfg r.WindowConfig) !none

    Opens a window for `root` and returns when no window is open any more: an application's main. Windows opened meanwhile with openWindow get their events and frames from the same loop. A window closes when the user asks unless a closeRequested handler cancels; a hidden window (visible false) stays open, so an application in the tray keeps running.

  • commitasync def run<T: component>(root T) !none

    Opens a window for `root` with the `UIConfig.defaultWindow` settings and returns when the user closes it.

  • commitdef pumpEvents(win r.Window) !r.PlatformEvent[]

    Reads the platform events waiting on the window and emits the `std.ui` event for each. Returns the raw events it consumed.

  • commitdef pumpAll() !none

    pumpEvents for every open window.

  • querydef displays() Display[]

    Every monitor attached to the machine, the primary one first.

    In a browser there is one, the screen the page is on, unless the user grants the Window Management permission.

  • querydef appearance() Appearance

    The platform's look settings: colour scheme, accent, contrast, motion, text size and fonts.

  • querydef theme() Theme

    The theme styles read when no other is given: the platform's scheme, accent, contrast and fonts with the std light or dark palette.

    It is one object, kept in step with appearanceChange.

  • querydef setTheme(theme Theme)

    Makes `theme` the one styles read when no component above theirs provides another (`Themed`, a window opened with a theme): `theme()` returns it from now on, and every style takes again what its body assigns from the theme on the next frame.

    A theme given this way is the program's: it keeps its values when the platform's look changes.

  • querydef display(win r.Window) !Display

    The display the window is on: the one holding most of it.

    Fails when the service is not running or the window is unknown.

  • emitdef displaysChange(displays Display[]) Display[]

    Emitted when a monitor is added or removed, or one changes resolution or scale.

  • emitdef appearanceChange(appearance Appearance) Appearance

    Emitted when a look setting changes, such as the user switching to dark mode.

  • emitdef themeChange(theme Theme) Theme

    Emitted when `setTheme` gave styles another theme.

  • querydef addIcons(set IconSet)

    Makes the icons of `set` known by name to components (`Icon("save")`, `Button(icon = "save")`). A set added later is looked in first; one with the name of a set already there replaces it.

  • querydef icon(name string) ?canvas.Picture

    The picture of the icon called `name` (`set/name` for the one of that set); none when no set holds it.

  • querydef showTray(icon image.Image, tooltip string = "", menu MenuEntry[] = []) !none

    Shows the application's icon in the system tray, or replaces what the tray shows. `menu` opens from the icon. Fails when the desktop has no tray. From any task, as openWindow.

  • querydef hideTray() !none

    Removes the tray icon. From any task, as openWindow.

  • emitdef closeRequested(request CloseRequest) CloseRequest

    Emitted when the user asks to close a window, before it closes. A handler calls request.cancel() to keep it open, and may set the window's visible to false to send it to the tray.

  • emitdef windowStateChanged(window r.Window) r.Window

    Emitted when a window is minimized, maximized, restored or made fullscreen.

  • emitdef trayActivated() bool

    Emitted when the tray icon itself is clicked. The SDL3 host reports menu lines only, so it does not emit it.

  • emitdef windowDisplayChange(evt WindowDisplayEvent) WindowDisplayEvent

    Emitted when a window moves to another display, or its display changes scale.

  • emitdef click(evt ClickEvent) ClickEvent

    Emitted for a click. target is the component under it, the window's root when no other component is there.

  • emitdef mouseMove(evt MouseMoveEvent) MouseMoveEvent

    Emitted when the pointer moves, resolved to the component under it.

  • emitdef mouseWheel(evt MouseWheelEvent) MouseWheelEvent

    Emitted for a wheel or trackpad scroll.

  • emitdef resize(evt ResizeEvent) ResizeEvent

    Emitted when a window is resized.

  • emitdef close(evt CloseEvent) CloseEvent

    Emitted when the user asks to close a window.

  • emitdef keyDown(evt KeyEvent) KeyEvent

    Emitted when a key goes down.

  • emitdef keyUp(evt KeyEvent) KeyEvent

    Emitted when a key comes up.

  • emitdef textInput(evt TextInputEvent) TextInputEvent

    Emitted for typed text.

Vault

The application's encrypted key/value store: credentials, tokens, keys and settings kept between runs, in one file under the application's home folder, bound to the machine, the user and the application (no password; the file copied elsewhere does not open). Secrets go in and come out as Secret<T> (put/get), values come back plain (store/load); remove, has, list; permissions adm.vault.read and adm.vault.write.

What the binding protects: the file read on another machine, by another user of this machine, or by another application. It does not protect against code running as this user on this machine, which can derive the same key. The machine identity is read on Linux today; on macOS and Windows the service does not start yet.

A name is not empty and made of A-Z, a-z, 0-9, ., _ and -, with dots separating its parts; an application and the libraries it uses share one name space. A value is of any type that crosses a process link (numbers, strings, arrays, maps, optionals, tuples, unions, structs, @portable types, types with toWire/fromWire) and is stored in the IPC wire encoding.

@persist(key) fields (of types, services and components) and WindowConfig.persist keep their values here, under persist.<key>. The vault is opened the first time such a value is restored or kept, or when the program starts if a service has a kept field. An application sets VaultConfig when it likes: given after the vault opened, the settings are applied to the open file.

A private key (generateKey, importKey) never comes out: the vault signs, decrypts and agrees with it behind adm.vault.keys and gives its public key. The service starts manually; VaultConfig (path, memoryKiB = 65536, passes = 3: the Argon2id cost of opening it) is what the vault starts with when config is called before start. Called on a started vault, config applies the settings to the open file where they differ from it: another cost wraps the vault's key again with a new salt (one key derivation at the new cost, every entry kept), another path moves the file there (an empty path is the default one; a file already at the new path is an error). Equal settings write nothing, and a change that fails leaves the vault as it was. It declares no events.

  • commitdef put<T>(name string, value Secret<T>) !none

    Keeps value as the secret name, replacing what was there. Fails when the vault is not started, the name is not one, or the file cannot be written. Needs adm.vault.write.

  • commitdef get<T>(name string) !?Secret<T>

    The secret name, none when there is none. Fails when the vault is not started, the entry was stored as a value, or it does not decode as T. Needs adm.vault.read.

  • commitdef store<T>(name string, value T) !none

    Keeps value under name, replacing what was there. Fails as put does. Needs adm.vault.write.

  • commitdef load<T>(name string) !?T

    The value name, none when there is none. Fails when the vault is not started, the entry was stored as a secret, or it does not decode as T. Needs adm.vault.read.

  • commitdef remove(name string) !none

    Removes the entry name; nothing happens for a name that is not there. Fails when the vault is not started or the file cannot be written. Needs adm.vault.write.

  • querydef has(name string) !bool

    Whether an entry called name is there. Fails when the vault is not started. Needs adm.vault.read.

  • querydef id() !string

    The vault's id: a random UUID (version 4) written into the vault's file when the file is made, the same for every caller and every run afterwards. It names this installation of the application, not the user or the machine, and is the one id every service relies on (Telemetry sends it as app.installation.id, Features as the subject's id), so whoever receives both can join them. Names under vault. are the vault's own and cannot be written or removed. Fails when the vault is not started. Needs adm.vault.read.

  • querydef list(prefix string = "") !string[]

    The names that start with prefix, sorted; every name for "". Fails when the vault is not started. Needs adm.vault.read.

  • commitdef generateKey(name string, kind KeyKind) !none

    Makes a new key of kind (Ed25519, EcdsaP256, EcdsaP384, Rsa2048, Rsa3072, X25519) and keeps it as name, replacing what was there. Fails as put does. Needs adm.vault.write.

  • commitdef importKey(name string, key PrivateKey) !none

    Keeps key as name, replacing what was there. Fails as put does. Needs adm.vault.write.

  • commitdef removeKey(name string) !none

    Removes the key name; nothing happens for a name that is not there. Fails as remove does. Needs adm.vault.write.

  • querydef publicKey(name string) !PublicKey

    The public half of the key name. Fails when the vault is not started or name is not a key. Needs adm.vault.read.

  • commitdef sign(name string, message byte[], hash Hash = Hash.SHA256) !byte[]

    Signs message with the key name: Ed25519 signs the message itself; ECDSA signs its hash digest and gives the ASN.1 signature; RSA signs the digest with PKCS#1 v1.5. Fails when the vault is not started, name is not a key, or the key's kind does not sign (X25519). Needs adm.vault.keys.

  • commitdef decrypt(name string, ciphertext byte[], hash Hash = Hash.SHA256) !byte[]

    Decrypts ciphertext with the RSA key name (OAEP over hash). Fails when the vault is not started, name is not a key, the key is not RSA, or the ciphertext does not open. Needs adm.vault.keys.

  • commitdef agree(name string, peer PublicKey) !byte[]

    The shared secret of the X25519 key name and peer. Fails when the vault is not started, name is not a key, or either key is not X25519. Needs adm.vault.keys.

Annotations

Each entry shows the annotation as it is written at a use site - the meta context parameter and any type parameter inferred from the target are not spelled out.

@migration

Application · targets functions of the application block

@migration(from string, to string = "")
@migration(from string, db string, to string = "")
@migration(from string, db string[], to string = "")

Marks a step that upgrades the application's home folder from one version to another; see Application.home() and Application home & migrations.

With db the step upgrades a database of the Database service instead: the connection it names ("" the default one), or several that it upgrades together once all are added. It takes no arguments and runs when the connection is added, in a transaction on it; a sql def step runs its statements there (a script of several statements sets a schema up in one step), a function step calls sql def functions inside a transaction { } block. See Database.add.

from is the version whose data the step understands; to is the version it produces, or the next declared step's from when omitted. Steps run in ascending order, and only those newer than the version that last ran the folder. The annotated function takes the home folder path and returns !none; a failing step restores the snapshot taken before the first step.

migration.adm
application MyApp {
	use std.services::(migration)

	@migration(from = "3.54", to = "4.0")
	def settingsToJson(home string) !none { ... }

	@migration(from = "4.0", to = "5.63")
	def splitCache(home string) !none { ... }
}

@allowDowngrade

Application · targets the application block

@allowDowngrade()

Lets the application open a home folder last written by a newer version of itself. Without it, a downgrade fails in Application.home().

downgrade.adm
@allowDowngrade()
application MyApp {
	use std.services::(allowDowngrade)
	...
}

@cache

Cache · targets methods

@cache(key string, ttl ?duration = none, idle ?duration = none, refresh = false)

Serves the method's result from the cache under key.

A live entry is returned and the body does not run; otherwise the body runs and its result is stored. With refresh the body always runs and replaces the entry.

ttl and idle go to Cache.set.

cache.adm
use std.services.cache::(cache)

type Users {
	@cache("user.{email}", ttl = 5m)
	def load(email string) !User {
		return try db.fetchUser(email)
	}
}

@cacheInvalidate

Cache · targets methods

@cacheInvalidate(key ...Glob)
@cacheInvalidate(key ...string)

Removes every key the selectors match once the method has run.

A pattern may interpolate the method's arguments.

cacheinvalidate.adm
use std.services.cache::(cacheInvalidate)

type Users {
	@cacheInvalidate("user.{email}")
	def rename(email string, name string) !none {
		return try db.rename(email, name)
	}
}

@config

Config · targets variables, fields

@config(key string, defaultValue ?any = none)

Fills the variable or the field from the configuration key, converted to its type as Config.get<T> converts it.

A module variable is filled when the program first reads it and again whenever its key changes. An optional variable reads none when the key is missing; a non-optional one with no default makes configuration loading fail.

A field is filled when its object is made and whenever it is assigned: while its key has a value, that value is what the field holds. A field whose key is missing takes the default, and keeps what it holds when there is none.

config.adm
use std.services.config::(config)

@config("server.port", 8080)
let port = 0                 // 8080 until a source defines server.port

@config("server.host")
let host ?string = none

type Pool {
	@config("db.pool.size", 4)
	size int = 1
}

@trace

Debugger · targets functions, methods

@trace()

Records the function's calls while a trace is running.

Each call logs when it started, how long it took, who called it and what error it returned.

Tracing itself is started with Debugger.traceStart and stopped with Debugger.traceStop.

trace.adm
use std.services.debugger

@debugger.trace()
def handleRequest(id string) !none {
	return try route(id)
}

@noinline

Prelude · targets functions, methods

@noinline()

Keeps the compiler from inlining the function, so it keeps its own frame in a debugger and in profiles.

A prelude annotation: it needs no import.

noinline.adm
@noinline()
def hotPath(n int) int {
	return n * 2
}

@noOptimize

Debugger · targets functions, methods

@noOptimize()

Compiles the function without optimisation.

It is not inlined either, so a debugger sees it as written.

nooptimize.adm
use std.services.debugger

@debugger.noOptimize()
def underInvestigation(n int) int {
	return n * 2
}

@trackCaller

Debugger · targets functions, methods

@trackCaller()

Reports the caller's location instead of the function's own in diagnostics.

For helpers that fail on the caller's behalf, so the message points at the call, not at the helper.

trackcaller.adm
use std.services.debugger

@debugger.trackCaller()
def logHere(message string) {
	println("{__file_name__}:{__line__} {message}")
}

@watch

Debugger · targets fields, variables

@watch(mode WatchMode = WatchMode.Write)

Emits Debugger.watched on every access to the field.

mode picks reads, writes or both.

Every access becomes a call, so leave it out of release builds. On a field of a type, struct, service or component, or on a module variable; what builds the object (its defaults, a service's first values) is no access.

watch.adm
use std.services.debugger::(watch, WatchMode, Debugger, WatchEvent)

type Session {
	@watch(WatchMode.Write)
	token string
}

@on(Debugger.watched)
def changed(ev Event<WatchEvent>) {
	println("{ev.result.name}: {ev.result.previous} -> {ev.result.value}")
}

@metric

Diagnostics · targets fields

@metric(name string, unit string = "", description string = "")

Registers a metric using the annotated field's metric type and assigns the resulting metric handle to the field.

If a metric with the same name and type already exists, the existing metric is used. Registration fails if the name is already registered with a different metric type.

The field’s own type selects the metric kind - no type argument is written.

metric.adm
use std.services.diagnostics::(metric)
use std.metrics::(Counter, Gauge)

type Uploads {
	@metric("uploads.total")
	total Counter

	@metric("uploads.inflight")
	inflight Gauge
}

@measureCount

Diagnostics · targets methods

@measureCount(name string)

Tracks how many times the annotated method is invoked.

Registers a Counter under the specified metric name and increments it once for every method invocation.

measurecount.adm
use std.services.diagnostics::(measureCount)

type Uploads {
	@measureCount("uploads.calls")
	def upload(data byte[]) !none {
		return try store(data)
	}
}

@measureTime

Diagnostics · targets methods

@measureTime(name string)

Tracks the execution duration of the annotated method, in nanoseconds.

Registers a Histogram under the specified metric name and records the duration of every completed method invocation.

measuretime.adm
use std.services.diagnostics::(measureTime)

type Uploads {
	@measureTime("uploads.latency")
	def upload(data byte[]) !none {
		return try store(data)
	}
}

@measureErrors

Diagnostics · targets methods

@measureErrors(name string)

Tracks failed executions of the annotated method.

Registers a Counter under the specified metric name and increments it when the method returns an error. The original error is propagated unchanged.

measureerrors.adm
use std.services.diagnostics::(measureErrors)

type Uploads {
	@measureErrors("uploads.failures")
	def upload(data byte[]) !none {
		return try store(data)
	}
}

@measureConcurrency

Diagnostics · targets methods

@measureConcurrency(name string)

Tracks the number of concurrently executing invocations of the annotated method.

Registers a Gauge under the specified metric name. The gauge is incremented when the method begins execution and decremented when the method completes, including when the method returns an error.

measureconcurrency.adm
use std.services.diagnostics::(measureConcurrency)

type Uploads {
	@measureConcurrency("uploads.active")
	def upload(data byte[]) !none {
		return try store(data)
	}
}

@log

Logging · targets functions, methods

@log(level LogLevel = LogLevel.Debug)

Writes a log entry each time the function is called, before its body runs.

The message is the function's qualified name and the arguments go in as fields under the parameters' names, one marked @sensitive() as ***.

log.adm
use std.services.logging
use std.services.logging::(LogLevel)

@logging.log(LogLevel.Info)
def createOrder(id string) !none {
	return try persist(id)
}

@logErrors

Logging · targets functions, methods

@logErrors(level LogLevel = LogLevel.Error)

Writes a log entry each time the function fails.

The message is the function's qualified name followed by failed; the fields are the arguments, as @log writes them, and error, the failure's message. The error itself goes on to the caller unchanged.

logerrors.adm
use std.services.logging::(logErrors)

@logErrors()
def charge(order string, amount int) !none {
	return try gateway.pay(order, amount)
}

@logSlow

Logging · targets functions, methods

@logSlow(threshold duration, level LogLevel = LogLevel.Warning)

Writes a log entry each time a call of the function takes longer than threshold.

The message is the function's qualified name followed by was slow; the fields are the arguments, as @log writes them, and duration, how long the call took.

logslow.adm
use std.services.logging::(logSlow)

@logSlow(250ms)
def search(query string) string[] {
	return index.find(query)
}

@logContext

Logging · targets types, functions, methods

@logContext(fields map<string, any>)

Attaches fields to every log entry written from the function, the method, or any method of the type: the write calls in the body, and the function's own @log, @logErrors and @logSlow entries.

A closer @logContext or a field passed to the call itself wins on the same key: a method's fields over its type's, the call's over both. Entries written by functions the annotated code calls do not carry the fields. On a type the fields are literals: text, numbers, true and false, and maps of those.

logcontext.adm
use std.services.logging::(Logging, logContext)

@logContext({"component": "billing"})
type Till {
	def ring(amount int) {
		Logging.info("rung", {"amount": amount})   // component=billing amount=12
	}

	@logContext({"op": "refund"})
	def refund(order string) {
		Logging.info("refunded", {"order": order})   // component=billing op=refund order=A-17
	}
}

@sensitive

Logging · targets parameters, fields

@sensitive()

Marks a value that must not show up in developer output.

Logs, traces, watches, crash reports and the IDE inspector print it redacted.

A marked field prints as *** wherever its value is printed (println, string interpolation, str()) or logged, alone or inside another value, an array or a map; a marked parameter is written as *** by @log, @logErrors and @logSlow. The codecs (JSON and the others) still write the real value, and so does code that reads the field. The debugger's variable view shows the real value: it is where a marked value is inspected. A Secret<T> value is never written to a log entry either: it is *** as a field, as a logged argument, and inside any logged value, while the codecs still write what it holds.

sensitive.adm
use std.services.logging::(Logging, log, sensitive)

struct Credentials {
	username string
	@sensitive()
	password string
}

@log()
def login(email string, @sensitive() password string) !Session {
	return try authenticate(email, password)   // logged as login email=a@b.c password=***
}

println(Credentials{username: "ann", password: "hunter2"})   // {ann ***}

@requires

Prelude · targets functions, methods

@requires(permission string)

Gates the function on a permission a service declares: every package on the calling chain must hold it, or the call fails with PolicyDenied. The function must return an errorable.

A prelude annotation: it needs no import. The service that declares the permission must be part of the program.

requires.adm
use std.net.http

@requires("adm.network.connect")
def fetch(url string) !http.Response {
	return try http.get(url)
}

@cron

Scheduler · targets functions

@cron(cronExpression string, name string = "")

Calls the function whenever the cron expression fires.

cron.adm
use std.services.scheduler::(cron)

@cron("0 3 * * *")
def nightlyCleanup() {
	purgeTempFiles()
}

@at

Scheduler · targets functions

@at(timestamp time.Time, name string = "")

Calls the function once, at the given time.

@after

Scheduler · targets functions

@after(interval duration, name string = "")

Calls the function once, after the duration.

after.adm
use std.services.scheduler::(after)

@after(30s)
def warmUp() {
	primeCaches()
}

@runEvery

Scheduler · targets functions

@runEvery(interval duration, name string = "")

Calls the function every interval.

runevery.adm
use std.services.scheduler::(runEvery)

@runEvery(5m)
def heartbeat() {
	println("alive")
}

@subscribe

Messaging · targets functions and methods

@subscribe(topic string = "", group string = "", workers int = 1)

Subscribes the function to a topic: the Messaging service calls it with each message.

The function takes one parameter. A Message gets every message of the topic. Any other type gets the payload of the messages that carry one of that type, and the topic may then be left out: it is the topic of the type.

topic is a topic or a pattern. Subscriptions with the same topic and group share the messages. workers is how many tasks call the function at once. A function that fails gets the message again.

Functions and methods of services are subscribed when the service starts; a method of a type is subscribed for each value given to Messaging.attach.

subscribe.adm
use std.services.messaging::(Message, subscribe)

@subscribe("orders.*", group = "billing", workers = 4)
def bill(message Message) !none {
	try charge(message.payload)
}

@topic

Messaging · targets types

@topic(name string)

Names the topic the values of a type are published on, for Messaging.publish(value) and for @subscribe functions that take the type. Without it the topic is the type's qualified name.

topic.adm
use std.services.messaging::(Messaging, subscribe, topic)

@topic("stock.counted")
struct StockCounted {
	units int
}

@subscribe()
def record(count StockCounted) {
	println(count.units)
}

try Messaging.publish(StockCounted{units: 3})

@translate

I18n · targets fields of type string

@translate(key string, params map<string, any> = {}, lang string = "")

Makes the field translated text: every read returns the message key from the catalogs of the I18n service in the language in use, or in lang when given, with params written in. The field's own value is the source text: what a read returns, with params written in, when no catalog has the key. params are evaluated on each read, in the field's scope. Imported from std.i18n.

Nothing is stored: a read after I18n.setLanguage returns the text in the new language, and interface code reads again on I18n.changed. A field a library declares is looked up in the library's own catalogs first, then in the application's.

translate.adm
use std.i18n::(translate)

type Basket {
	items Item[]

	@translate("basket.title")
	title string = "Your basket"

	@translate("basket.count", params = {"count": items.len()})
	summary string = "{count, plural, one{# item} other{# items}}"

	@translate("basket.title", lang = "en")
	titleInEnglish string = "Your basket"
}

@span

Telemetry · targets functions and methods

@span(name string = "")

Times each call as a span, failed when the call fails. The span is the current one while the function runs, so the spans of what it calls are its children.

name is the span's name; the function's qualified name when empty.

span.adm
use std.services.telemetry::(span)

@span()
def checkout(cart Cart) !Receipt {
	return try charge(cart)
}