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.
|
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:
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.
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.
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.
-
query
def files(name string = "") !stringThe folder holding a package's shipped files, its
[package] files: the calling package's whennameis empty, else the library namednamespace:name(local:namefor one installed from a file). Once the program is installed that isshare/<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. -
query
def filePath(path string) !stringResolves a package path:
package://assets/logo.pngis a file the calling package shipped,acme:imaging://assets/logo.pngone 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 withadm runfinds the files of its source tree. EveryStoragemethod, and so everystd.oscall, resolves its path this way, soos.readFile("package://assets/logo.png")works fromadm run, from a build in the tree and from an installed program alike. Fails when the package has no shipped files. -
query
def pid() intReturns process id of the application
-
query
def executablePath() stringReturns path to the application executable.
-
query
def startTime() TimeReturns time when the application process started.
-
query
def name() stringReturns the name the application is known by: the
adm.tomlslug when one is configured, else the declared application name. -
commit
def home() !stringReturns the application's home folder, creating it on first use and bringing it up to date with this version: the
versionmarker, a snapshot ofdata/and the@migrationchain. 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. -
query
def version() stringReturns application version, or an empty string if the application is not versioned.
-
query
def homePath() !stringReturns 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 noversionmarker 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 tobackup/<old>/, then every@migrationstep 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, orUSERPROFILEon Windows). The pathhome()would use, without creating or upgrading the folder. For code that reads an optional file there (the Policy service'spolicy.toml) and must not leave a folder behind. -
query
def build() stringReturns application build number, or an empty string if unavailable.
-
query
def instanceId() stringReturns identifier of the current application instance.
The identifier is unique to this running instance and changes between runs.
-
query
def arguments() string[]Returns the command-line arguments the application was started with, the program's own path first.
-
query
def uptime() durationReturns how long the application has been running.
-
commit
def shutdown(code int = 0) !noneRequests graceful application shutdown:
shutdownRequestedis 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. -
query
def shuttingDown() boolReturns whether graceful application shutdown has been requested.
-
emit
def shutdownRequested(code int) intEmitted when graceful application shutdown is requested, before the services are stopped. Handlers that are not
asynchave run to their end when the services stop. -
commit
def 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.
-
query
def available() boolWhether the program runs in a browser.
-
query
def info() !BrowserInfoThe browser, its version and engine, the platform it runs on and the user's languages, preferred first.
Fails off the web.
-
query
def location() !url.URLThe page's address.
Fails off the web.
-
commit
def navigate(to url.URL, replace bool = false) !noneGoes to another address, leaving the program;
replaceleaves no history entry behind. In-app navigation is the Router's.Fails off the web.
-
commit
def back() !noneGoes 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.
-
emit
def locationChange(to url.URL) url.URLThe history entry changed: back or forward, or in-app navigation.
-
query
def metadata() !PageMetadataWhat the document's head holds: title, description, canonical address, sharing picture, language, robots, theme colour and further
<meta>pairs.setMetadatawrites them; an empty field removes what the head had for it.Fails off the web.
-
commit
def confirmLeave(on bool) !noneMakes the browser ask before the tab is closed or navigated away (unsaved changes), or stop asking.
Fails off the web.
-
query
def cookies() !Cookie[]The site's cookies the page can read (not the HttpOnly ones);
cookie(name)one of them,setCookieandremoveCookiewrite. Needs theadm.browser.cookiespermission.Fails off the web, or when the policy denies
cookies. -
query
def storage(key string, session bool = false) !?stringThe value under
keyin the site's storage (localStorage, or sessionStorage withsession);setStorageandremoveStoragewrite. Needsadm.browser.storage.Fails off the web, or when the policy denies
storage. -
query
def permission(name string) !PermissionStateThe browser's answer for a permission:
"camera","notifications","clipboard-read", ...Fails off the web, or when the browser does not know the permission.
-
commit
def install() !boolOffers 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. -
commit
def registerWorker(script url.URL) !noneRegisters the service worker that serves the site offline;
updateReadyis emitted when a new version waits, andapplyUpdate()makes it take over.Fails off the web, or when the browser refuses the worker.
-
commit
def evaluate(script string) !stringRuns 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.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
-
commit
atomic 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.
-
query
atomic def get(key string) ?anyThe value under the key, or none when there is none or it has expired.
Reading restarts the idle timer.
-
query
atomic 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.
-
query
atomic def get(selector Glob) map<string, any>The values under every key the selector matches.
Reading restarts each key's idle timer.
-
commit
atomic def delete(key string)Removes the key.
-
commit
atomic def delete(keys string[])Removes the keys.
-
commit
atomic def delete(selector Glob) intRemoves every key the selector matches and returns how many went.
-
commit
atomic 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.
-
commit
atomic def renew(selector Glob, ttl ?duration = none, idle ?duration = none) intRenews every key the selector matches and returns how many.
A ttl counts from now; an idle time restarts each idle timer.
-
commit
atomic def take(key string) ?anyRemoves the key and returns what it held, or none when nothing live was there.
-
commit
atomic def touch(key string)Restarts the key's idle timer; the ttl is untouched.
-
commit
atomic def touch(keys string[])Restarts the idle timer of each key; ttls are untouched.
-
commit
atomic def touch(selector Glob) intRestarts the idle timer of every key the selector matches and returns how many.
-
query
atomic def has(key string) boolWhether the key is live.
-
query
atomic def has(keys string[]) map<string, bool>Whether each key is live.
-
query
atomic def has(selector Glob) boolWhether any live key matches the selector.
-
query
atomic def ttl(key string) ?durationHow long the key has left to live, or none when it is gone or has no ttl.
-
query
atomic 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.
-
query
atomic def ttl(selector Glob) map<string, ?duration>How long each matching key has left to live; none for one with no ttl.
-
query
atomic def idle(key string) ?durationHow long the key may still go unread, or none when it is gone or has no idle time.
-
query
atomic 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.
-
query
atomic def idle(selector Glob) map<string, ?duration>How long each matching key may still go unread; none for one with no idle time.
-
query
atomic def keys() string[]Every live key.
-
query
atomic def keys(selector Glob) string[]Every live key the selector matches.
-
query
atomic def size() intHow many keys are live.
-
query
atomic def size(selector Glob) intHow many live keys the selector matches.
-
commit
atomic def clear()Empties the cache.
-
emit
def expired(key string) stringEmitted when a key expires.
-
emit
def deleted(key string) stringEmitted when a key is removed by a call rather than by expiry.
-
emit
def created(key string) stringEmitted 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.
-
query
def add(name string, target string, limit int = 8, versions string = "") !noneRegisters a connection under a name: a pool of up to
limitconnections 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 withversionsin 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). -
query
def add(target string) !noneRegisters and migrates the default connection, the one
get()returns. -
query
atomic def versionsTable(name string) ?stringThe table a connection's migrations keep its version in; none when it is kept in the home folder.
-
query
atomic def get(name string) !db.PoolThe connection registered under a name.
-
query
atomic def get() !db.PoolThe default connection: the one added without a name, else the only one registered.
-
query
atomic def names() string[]The registered names, the default's as "".
-
query
def exec(statement string, args ...any) !db.ResultRuns a statement on the default connection.
-
query
def query(statement string, args ...any) !db.RowsRuns a statement on the default connection and returns its rows.
-
query
def remove(name string) !noneCloses 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.
-
commit
def copy(text string) !nonePuts 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.
-
commit
def paste() !stringReturns 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.
-
commit
def clear() !noneEmpties the clipboard.
-
query
def has() !boolWhether the clipboard holds any text. Needs the read permission.
-
query
def capabilities() ClipboardCapabilitiesReports whether this machine has a clipboard program and which one:
wl-clipboard,xclip,xselorpbcopy;windowsfor the Windows clipboard,browseron the web. -
commit
def watch(period duration = 500ms) !noneStarts watching the clipboard: a task reads it every
periodand emitschangedwhen 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.
-
commit
def unwatch()Stops the watcher task; nothing happens when none runs.
-
query
def watching() boolWhether the watcher task runs.
-
emit
def changed(change ClipboardChange) ClipboardChangeEmitted by the watcher when the clipboard text changed.
ClipboardChangecarries the text ("" when emptied), whether this program put it there throughcopy, and when the watcher noticed. -
emit
def reading(request ClipboardRequest) ClipboardRequestEmitted before the clipboard is read, inside
pasteandhas: a handler that has the clipboard at hand callsrequest.answer(text). Unanswered, the clipboard program is used. -
emit
def writing(request ClipboardRequest) ClipboardRequestEmitted before the clipboard is written, inside
copyandclear: a handler that putrequest.texton the clipboard ("" empties it) callsrequest.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.
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. |
-
commit
def switchProfile(profile ?string) !noneSwitches to the profile and reloads every source.
When the reload fails the old profile and its values stay in place. Fails as
reloaddoes. -
query
def profile() ?stringThe active profile, or none when no profile is selected.
-
query
def get(key string) ?anyThe 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>. -
query
def get<T>(key string) !?TThe 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.
-
query
def has(key string) boolWhether some source or an override defines the key, or a key below it.
-
commit
def 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.
-
commit
def reset(key string)Drops the override on the key and on the keys below it.
The key goes back to whatever the sources say.
-
commit
def reload() !noneReloads every source.
Fails when a source that is not optional cannot be loaded, and as
startdoes for the program's@configvariables: the values of before then stay. A source that fails is emitted asreloadFailedwhether or not it is optional. -
query
def sources() SourceConfig[]The sources, highest precedence first.
-
query
def source(name string) ?SourceConfigThe source with that name, or none.
-
query
def sourceOf(key string) ?ConfigSourceThe source the key's value comes from; none for an override or an unknown key.
-
query
def lastLoaded() time.TimeWhen the sources last loaded without error; the zero time before that.
-
emit
def changed(change ConfigChange) ConfigChangeEmitted when a key's value changes: by a reload, a change of profile or of sources,
setorreset. The first load of a run emits every key it defines.A reload that leaves the value as it was emits nothing.
-
emit
def reloadFailed(event ConfigReloadError) ConfigReloadErrorEmitted 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.
-
commit
def profileStart(kind ProfileKind) !ProfileIdStarts 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.
-
commit
def profileStop(id ProfileId) !ProfileStops the profile and returns what it collected.
Fails when id names no running profile.
-
commit
def traceStart() !TraceIdStarts recording the calls of every
@tracefunction and method, and returns the trace's id.Fails when a trace already runs, and in release builds.
-
commit
def traceStop(id TraceId) !TraceStops the trace and returns what it recorded.
Fails when id names no running trace.
-
queryplanned
def stackTrace() errors.StackTraceThe stack trace of the calling task.
-
query
def heapSnapshot() !HeapSnapshotTakes a heap snapshot: the allocations live now and the bytes they hold, and, while a Heap profile runs, its live allocations by stack.
-
commitplanned
def pause() !noneBreaks into the debugger when one is attached; otherwise does nothing.
-
emit
def watched(event WatchEvent) WatchEventEmitted 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.
-
query
def list(kind ?DeviceKind = none) !Device[]Every connected device, or only those of one kind.
Fails when the policy denies listing the devices.
-
query
def 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. -
query
def get(id string) !?DeviceThe device with that id, or none.
Fails when the policy denies listing the devices.
-
commit
def watch(period duration = 1s) !noneStarts 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.
-
commit
def unwatch() !noneStops watching.
Fails when not watching.
-
query
def watching() boolWhether the watching task runs.
-
emit
def connected(device Device) DeviceEmitted when a device appears.
-
emit
def disconnected(device Device) DeviceEmitted when a device goes away.
-
commit
def open(id string) !intOpens 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 itsdevicerules (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.
-
query
def opened(fd int) ?DeviceThe device a descriptor
openreturned 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. -
commit
def close(fd int) !noneCloses a descriptor
openreturned. Fails when it is not open.
Diagnostics
Named metric registry - counters, gauges, histograms, and toggles.
-
commit
def register<T: Metric>(name string, labels map<string, string> = {}, unit string = "", description string = "") !TRegisters a metric under the specified name.
labelstell the series of one metric apart:register<Counter>("http.requests", {"route": "/orders"})once per route.unitsays what the value counts (ms,By,1) anddescriptionwhat 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.
-
query
def get(name string, labels map<string, string> = {}) ?MetricReturns the metric registered under the specified name and labels.
Returns none if no such metric exists.
-
query
def has(name string, labels map<string, string> = {}) boolReturns whether a metric with the specified name and labels is registered.
-
query
def list() map<string, Metric>Returns all registered metrics by name; one with labels is under
name{key="value",...}, the labels sorted by key. -
query
def names() string[]Returns the names
listholds the metrics under. -
query
def series() MetricSeries[]Returns every registered metric with its
name,labels,unit,descriptionandmetric, 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.
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. |
-
commit
def config(val FeaturesConfig) !noneApplies 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
retryortimeoutis not positive, and when the service is started and the settings have nourlor nokey. Needs the permissionadm.features.fetch. -
commit
def 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.
-
query
def enabled(name string, fallback bool = false) boolWhether the feature is on for this installation;
fallbackwhen the server did not mention it, no reply has arrived yet, or the reply held is past its validity. -
query
def value<T>(name string, fallback T) TThe feature's value as a
T: a number, a text, a bool, a struct read from an object, an array.fallbackwhen the server gave the feature no value or one that is not aT, and wheneverenabledwould give its fallback. -
query
def arm(group string) stringThe 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. -
query
def value<T>(group string, key string, fallback T) TThe value of one key of a group: what the installation's arm gives the key, the group's default outside the test,
fallbackfor a group or a key the server did not mention. -
query
def refresh() !noneAsks the server now and takes its reply. Fails with a
FeaturesError(kind:Transport,Reply,Signature,Subject,Expired) and keeps what it held. -
commit
def override(name string, enabled bool, value ?any = none)Makes this run answer
enabledandvaluefor 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. -
commit
def override(group string, arm string, values map<string, any> = {})Makes this run answer
armfor the group as given, andvaluefor the keys invalues; the other keys stay as the server gave them. -
commit
def restore(name string = "")Takes back the overrides of the feature and of the group of that name; every override for
"". -
emit
def changed(change FeaturesChange) FeaturesChangeEmitted when a reply gives a feature or a group something else than the reply before it:
featuresandgroupshold the names concerned. -
emit
def failed(failure FeaturesError) FeaturesErrorEmitted 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.
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. |
-
commit
def config(val I18nConfig) !noneApplies the settings. A started service loads the folder again when it changed, and emits
changedwhen the language or the region in use did.Fails when
language,regionorsourceis not a well-formed locale, and on a started service asstartdoes; the service then stays as it was. -
query
def language() LocaleThe language translated text is shown in: the calling task's when it set one, else the program's, else the system's.
-
query
def region() LocaleThe locale whose formats numbers, money, dates and times take, resolved the same way.
-
commit
def setLanguage(language string, scope I18nScope = I18nScope.Application) !noneSets the language; empty goes back to the system's, or for a task to the program's. For the program it emits
changedwhen the language in use becomes another.Fails when the language is not a well-formed locale.
-
commit
def setRegion(region string, scope I18nScope = I18nScope.Application) !noneSets 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.
-
query
def timeZone() TimeZoneThe time zone instants are written in: the calling task's when it set one, else the program's, else the system's.
-
commit
def setTimeZone(timeZone string, scope I18nScope = I18nScope.Application) !noneSets 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
changedwhen the zone in use becomes another. AZonedDateTimeis 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.
-
query
def system() SystemLocalesWhat 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. -
query
def languages() Locale[]The languages the application has catalogs for, and its source language.
-
query
def negotiate(acceptLanguage string) ?LocaleThe language of
languages()that best serves an HTTP Accept-Language header; none when nothing is close. -
commit
def load(path string) !noneLoads 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.
-
commit
def load(catalog Catalog) !noneLoads a catalog made in code. Fails when its locale is undetermined.
-
query
def text(key string, params map<string, any> = {}, language string = "", region string = "", context string = "") stringThe translated text of a message with the values of
paramswritten in: inlanguagewhen given, else the language in use; numbers and dates in it asregionwrites them when given, else the region in use.contexttells apart messages with the same key.When no catalog has the message, the answer is the key with the values written in, and
missingreports it. -
query
def textIn(owner string, key string, fallback string, params map<string, any> = {}, language string = "") stringThe same for the code of the module
owner, whose library's catalogs are looked in first;fallbackis the answer when no catalog has the message.@translatefields read through it. -
query
def has(key string, language string = "", context string = "") boolWhether a catalog has the message in the language or one close to it.
-
query
def number(value int | float | Decimal, region string = "") stringThe number as the region writes it:
1.234,5. -
query
def percent(value float | Decimal, region string = "") stringA fraction as a percentage: 0.25 is
25 %. -
query
def money(amount Decimal | float, currency string = "", region string = "") stringAn amount in a currency (ISO 4217), the region's own when none is given:
1.234,50 €. -
query
def money(minorUnits int, currency string = "", region string = "") stringAn amount given in the currency's minor units: 1999 with
EURis 19.99 euros. -
query
def date(value Time | ZonedDateTime | Date | DateTime, width DateTimeStyle = DateTimeStyle.Medium, region string = "", timeZone string = "") stringThe day:
12.10.2026, or longer or shorter bywidth. An instant is written in the time zone in use, or intimeZonewhen given (instants only); a zoned date-time in its own. -
query
def time(value Time | ZonedDateTime | TimeOfDay | DateTime, width DateTimeStyle = DateTimeStyle.Short, region string = "", timeZone string = "") stringThe time of day:
14:05. -
query
def dateTime(value Time | ZonedDateTime | DateTime, dateWidth DateTimeStyle = DateTimeStyle.Medium, timeWidth DateTimeStyle = DateTimeStyle.Short, region string = "", timeZone string = "") stringThe day and the time:
12.10.2026, 14:05. -
query
def spellOut(value int | float | Decimal, ruleSet string = "%spellout-numbering", region string = "") stringThe number in words, or through another rule set of the region's language (
%spellout-ordinal,%digits-ordinal). -
query
def plural(count int | float | Decimal, kind PluralType = PluralType.Cardinal, language string = "") PluralCategoryThe plural form the count takes in the language in use, or in
languagewhen given:Onefor 1 in English,Fewfor 3 in Romanian. The form belongs to the language of the text it picks; the region has no say in it. -
emit
def changed(change I18nChange) I18nChangeEmitted 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.
-
emit
def missing(text MissingText) MissingTextEmitted 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.
// 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.
-
commit
def provide<T>(instance T) !noneServes
instanceto other processes, for every interface its type is marked@remotefor, 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.
-
commit
def withdraw<T>(instance T) !noneStops serving
instance. Calls already running finish; new ones are answered as if the interface was never provided.Fails when it is not provided.
-
commit
def listen(address Endpoint, options Listener = Listener{}) !stringStarts accepting clients on
address:host:port,[v6]:portorunix:/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 andopenis not set. -
query
def close(address string) !noneStops accepting clients on
addressand closes its connections once their running calls finish.Fails when nothing listens there.
-
query
def connect<I>(address Endpoint, auth Auth = Auth{}, options Link = Link{}) !IReturns an
Iwhose methods run in the process ataddress.Fails with an
IPCError:Unreachablewhen the server cannot be reached,Refusedwhen it does not acceptauth,Mismatchwhen it provides another version ofIor none. -
query
def connect<I>(addresses string[], auth Auth = Auth{}, options Link = Link{}) !IReturns an
Iserved by several processes;options.balancedecides which one takes each call. Succeeds when one server accepts.Fails with an
IPCError:Unreachablewhen none can be reached,RefusedorMismatchwhen a reachable one refuses the client or provides another version ofI. -
query
def disconnect<I>(client I) !noneCloses the connections behind a value
connectreturned. Calls in flight fail withIPCErrorKind.Closed.Fails when
clientdid not come fromconnect. -
query
def join(cluster Cluster) !noneMakes 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 aboverefuseAbove. Listen first.Fails when this process is a node already, or when discovery cannot record it.
-
query
def leave() !noneLeaves the cluster: the other members are told, discovery forgets this node.
Fails when this process is in no cluster.
-
query
def members() Member[]The members of the cluster as this node knows them, itself first: address, interfaces, state, load.
-
emit
def joined(member Member) Member,def left(member Member) MemberA member became alive; a member left or fell silent too long.
-
query
def caller() ?CallerWho is calling, inside a method a client called; none anywhere else.
-
query
def links() LinkInfo[]The connections open now, accepted and made.
-
query
def 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.
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
| Encoder | Writes |
|---|---|
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.Encoder | RFC 5424 or RFC 3164 messages (std.net.syslog). |
journald.Encoder | Journal entries (std.proto.journald): MESSAGE, PRIORITY, the call site and each field under an upper-case name. |
| Writer | Sends 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.Client | A syslog daemon or collector over UDP, TCP, TLS or the local socket. |
journald.Client | The 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.
-
commit
def debug(message string, fields map<string, any> = {})Writes a log entry at debug level.
-
commit
def info(message string, fields map<string, any> = {})Writes a log entry at info level.
-
commit
def warning(message string, fields map<string, any> = {})Writes a log entry at warning level.
-
commit
def error(message string, fields map<string, any> = {})Writes a log entry at error level.
-
query
def fatal(message string, fields map<string, any> = {}, wait duration = 5s)Writes a log entry at fatal level and waits up to
waitfor the writers to have it, with everything written before it. The program goes on: ending it is the caller's decision. -
commit
def log(level LogLevel, message string, fields map<string, any> = {})Writes a log entry at the given level.
-
commit
def 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
LOGwhere the level stands. -
commit
def 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.
-
commit
def 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@logContextdoes. 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. -
query
def flush(timeout duration = 5s) !noneWaits until every record written so far has been handed to the writers.
Fails when that takes longer than
timeout. -
query
def enabled(level LogLevel) boolReports whether an entry at
levelwould be written, for code that builds costly fields only when they are used. -
query
def level() LogLevelThe lowest level that is written.
-
commit
def setLevel(level LogLevel)Sets the lowest level that is written.
Entries below it are dropped before they reach any writer.
-
query
def dropped() intHow many records were dropped because the queue was full (
LogConfig.capacity, 8192) or the service stopped. -
emit
def recorded(record LogRecord) LogRecordEmitted for each record the service accepted, as the writers get it: past the level filter, with its time and call site, the
@logContextfields merged in and theLogValueand@sensitivevalues replaced. It is emitted in the order the records were written, before the record goes to the writers. -
emit
def writeFailed(failure LogWriteFailure) LogWriteFailureEmitted 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.
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.
-
commit
def config(val MessagingConfig) !noneApplies the settings; messages already waiting keep the attempts they have had.
-
commit
def publish(payload any, topic string = "", priority int = 0, delay duration = 0s, headers map<string, string> = {}) !MessageIdPublishes payload and returns the message's id.
Without a topic the message goes to the topic of the payload's type: the name its
@topicgives, 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. -
commit
def subscribe(topic string, handler def (Message) !none, group string = "", workers int = 1) !SubscriptionIdSubscribes 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.
-
commit
def subscribe(topic string, group string = "") !SubscriptionIdSubscribes without a handler: the messages wait until
receivetakes them, and each is settled withackornack. -
commit
def attach(consumer any) !SubscriptionId[]Subscribes every
@subscribemethod of consumer's type, each run on consumer, and returns their subscriptions. -
commit
def unsubscribe(id SubscriptionId) !noneEnds the subscription. A
receivewaiting on it returns none; messages it took and did not settle go back to its group. -
query
def receive(id SubscriptionId, timeout ?duration = none) !?MessageReturns 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.
-
commit
def ack(message Message) !noneSettles a received message: it was handled.
-
commit
def nack(message Message, delay ?duration = none) !noneSettles a received message: it was not handled. It is delivered again after delay, or becomes a dead letter when that was its last attempt.
-
query
def 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.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
-
commit
def socket(domain net.SocketDomain, typ net.SocketType) !net.SocketOpens 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.
-
commit
def socketFd(domain net.SocketDomain, typ net.SocketType) !intOpens a socket and returns its raw descriptor, for code that works with descriptors rather than `std.net.Socket`.
-
query
def bind(fd int, addr Endpoint) !noneBinds 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.
-
query
def connect(fd int, addr Endpoint, timeout duration = 30s) !noneConnects 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.
-
query
def accept(fd int) !intAccept 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.
-
commit
def close(fd int) !noneClose a socket FD.
Closing an already-closed FD is an OS error; `std.net` wrappers typically expose `close()` and forward to this method.
-
commit
def listen(fd int, backlog int) !noneMark a bound stream socket as a listener.
`backlog` is passed through to the OS; its effective value may be clamped by the platform.
-
query
def write(fd int, data byte[], timeout duration = 30s) !intWrite 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.
-
query
def sendUrgent(fd int, value byte) !noneSends 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.
-
query
def urgentPending(fd int) !boolReports whether urgent data waits to be read on a stream socket FD, without blocking.
Fails when fd is not a socket.
-
query
def urgentInline(fd int, enabled bool) !noneMakes 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.
-
query
def read(fd int, data byte[], timeout duration = 30s) !intRead 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.
-
query
def 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`.
-
query
def resolverConfig() stringThe system's resolver configuration in the
resolv.confformat (/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. -
query
def hostsFile() stringThe text of the system's hosts file (
/etc/hosts; on WindowsSystem32\drivers\etc\hosts), "" when it cannot be read. Read without a storage permission, asresolverConfigis. -
query
def sendToNameServer(fd int, query byte[], server string, timeout duration = 5s) !intSends a DNS query to one of the system's name servers from a datagram socket. The server is not checked against the
connectpolicy, since resolving a name is part of connecting to it; the names the query asks about are, as hosts.Fails when
serveris not one ofdns.systemConfig().servers, the bytes are not a DNS query, the policy denies one of its names, or the send is refused or times out. -
query
def 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
sendToNameServerchecks.Fails as
sendToNameServerdoes, and when the connection fails, times out or closes before a whole response arrived. -
query
def dialStream(addr Endpoint, timeout duration = 30s) !intResolve + 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
connectoracceptdoes;setNoDelay(fd, false)changes that. -
query
def sendTo(fd int, data byte[], addr Endpoint, timeout duration = 30s) !intSend 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.
-
query
def send(fd int, data byte[], timeout duration = 30s) !intSends 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.
-
query
def receive(fd int, data byte[], timeout duration = 30s) !intReceives the next datagram from the peer a datagram socket is connected to and returns its length; one longer than
datais cut to it.Fails when fd is closed or the timeout passes.
-
query
def 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`.
-
query
def sendDescriptor(fd int, descriptor int, addr string = "", timeout duration = 30s) !noneHands 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.
addris 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, whichreceiveDescriptortakes 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. -
query
def receiveDescriptor(fd int, timeout duration = 30s) !intTakes 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). -
commit
def shutdown(fd int, how int = 2) !noneShutdown 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)
-
commit
def setReuseAddr(fd int, enabled bool = true) !noneEnable or disable SO_REUSEADDR.
This is typically required for servers that need to restart quickly.
-
commit
def setBroadcast(fd int, enabled bool = true) !noneEnable 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.
-
commit
def setNoDelay(fd int, enabled bool = true) !noneEnable or disable TCP_NODELAY (TCP only).
For non-TCP sockets this may fail with an OS error.
-
commit
def setKeepAlive(fd int, enabled bool = true) !noneEnable or disable SO_KEEPALIVE.
Keepalive behavior is OS-configured; this only toggles it.
-
commit
def setReusePort(fd int, enabled bool = true) !noneEnable or disable SO_REUSEPORT when supported by the platform.
Some OSes do not expose this option; in that case this returns an error.
-
commit
def setLinger(fd int, enabled bool, seconds int = 0) !noneControl SO_LINGER.
When enabled, `seconds` controls how long the OS may block during `close` in an attempt to deliver queued data.
-
commit
def setRecvBuf(fd int, bytes int) !noneSet SO_RCVBUF.
-
commit
def setSendBuf(fd int, bytes int) !noneSet SO_SNDBUF.
-
commit
def joinGroup(fd int, group string, iface string = "") !noneJoins a multicast group on a datagram socket, so that what is sent to the group reaches it:
224.0.0.251,ff02::fb.ifacenames 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.
-
commit
def leaveGroup(fd int, group string, iface string = "") !noneLeaves a multicast group joined with
joinGroup.Fails when the socket is no member of the group.
-
commit
def setMulticast(fd int, ttl int = 1, loop bool = true) !noneSets 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.
-
commit
def setSockOptInt(fd int, level int, opt int, val int) !noneSets an integer socket option by its `setsockopt` level and name, for options `std.net.Socket` does not wrap.
-
commit
def getSockOptInt(fd int, level int, opt int) !intRead an integer socket option.
-
query
def localAddr(fd int) !stringReturn 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.
-
query
def peerUserId(fd int) !intThe 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
fdis not a connected Unix socket, or on a platform that does not report it (Windows). -
query
def remoteAddr(fd int) !stringReturn the remote endpoint of a connected socket FD, formatted as an address string.
-
query
def publicAddr() !stringReturns 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.stunServersare 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. -
query
def netIface() !net.NetInterface[]Lists the host's network interfaces with their addresses and `std.net.NetIface*` flags.
-
query
def 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.
-
query
def socketInfo(fd int) ?SocketInfoReturns 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.
-
commit
def show(notification Notification) !NotificationIdShows a notification and returns the id the server gave it.
Notificationcarries 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.
-
commit
def show(title string, body string = "") !NotificationIdShows a notification with a title and an optional body and returns its id.
-
commit
def close(id NotificationId) !noneTakes a shown notification off the screen.
Fails when there is no connection or the server does not know the id.
-
commit
def ask(notification Notification, wait duration = 60s) !stringShows 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.
-
query
def capabilities() NotificationCapabilitiesReports what the notification server can do: actions, body text, markup, images, sound, persistence. Nothing is available when there is no server.
-
query
def shown() NotificationId[]The ids of the notifications shown by this program that the server has not closed yet.
-
emit
def action(event NotificationAction) NotificationActionEmitted when the user presses one of a notification's buttons; the event names the notification and the action id.
-
emit
def closed(id NotificationId) NotificationIdEmitted when a notification leaves the screen: dismissed, expired or closed.
Policy
Permissions and policy decisions.
-
commit
def registerSpec(name string, factory PolicySpecFactory) !noneRegisters the spec type a permission names, so the file's rules for it can be checked against arguments.
-
commit
def registerResolver(handler PolicyResolver) !noneRegisters 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.
-
commit
def registerResources(permission string, patterns string[]) !noneNames 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
askrule then never puts such a value to the user, who could not judge it; adenyrule 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.
-
commit
def configure(text string) !noneLoads 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.
-
query
def requirePermissions(permissions string[]) !noneFails unless every package on the call chain holds each permission.
-
commit
def revokePermissions(permissions string[]) !noneDrops permissions from the application's own grants for the rest of the run.
-
query
def hasPermissions(permissions string[]) boolWhether every package on the call chain holds each permission.
-
commit
def register(registration PolicyRegistration) !noneRegisters an owner's rules and validators for one action.
-
query
def evaluate(request PolicyRequest) !PolicyDecisionDecides a request and reports why: which package lacks the permission, or the merged rules that apply.
-
query
def explain(request PolicyRequest) !PolicyDecisionExplains a request: the decision, the rule and layer behind it.
-
query
def require(request PolicyRequest) !noneEvaluates 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.
-
query
def text() stringThe policy file as it would be written from the loaded rules and settings: one table per subject and permission, the comment preamble kept.
-
query
def validate(permission string, spec string, value string) !noneChecks 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.
-
query
def list() Permission[]Lists every permission the program's services declare.
-
emit
def decided(entry PolicyLogEntry) PolicyLogEntryEmitted for every denial, ask and live answer.
-
query
def promptText(permission string, value string) stringWhat 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.
-
commit
def open(address string) !noneOpens 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. Needsadm.runtime.open, which is narrower thanadm.runtime.spawn. Fails when the policy denies it, the address is empty or starts with-, or the opener cannot open it. -
query
def systemInfo() SystemInfoReturns information about the host system running the ADM runtime.
-
query
def workingDir() stringReturns current process working directory.
-
query
def memoryUsage() MemoryUsageReturns memory usage statistics for the current ADM runtime.
-
query
def cpuUsage() metrics.HistogramThe 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. -
query
def sizeOf(val any) intReturns 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.
-
query
def getEnv(key string) ?stringReturns value of the specified environment variable.
Returns none when the variable is not defined.
-
query
def environment() map<string, string>Returns every environment variable of the process as it is now, by name.
-
query
def services() ServiceInfo[]Returns runtime information for all registered services.
-
query
def serviceInfo(name service) ?ServiceInfoReturns runtime information for the specified service.
Returns none if the service is not registered with the runtime.
-
query
def loadPlugin(path string) ?PluginReads 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.
-
query
def 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. -
commit
def run(command string, input string = "") !ProcessResultRuns a command through the system shell with
inputas its standard input and returns what it printed and its exit code (128 + signal when killed, 127 when the shell could not start it). Needsadm.runtime.spawn.std.os.runandstd.os.outputwrap 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.
-
commit
def start(program string, args string[] = [], env map<string, string> = {}, dir string = "", errors bool = false) !os.ProcessStarts a program and returns while it runs.
programis a path, or a name looked up inPATH;argsfollow it as written, with no shell in between.envadds to or replaces variables of this program's environment for the started one, anddiris its working directory. The result'sinputandoutputpipes are the program's standard input and output; its standard error is this program's, or the pipeerrorswhenerrorsis 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 andkill()stops it. Needsadm.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.
-
commit
def setEnv(key string, value string) !noneSets value of the specified environment variable for the current process.
-
emit
def serviceStatusChanged(change ServiceStatusChange) ServiceStatusChangeEmitted when runtime status of a registered service changes.
Scheduler
Cron, one-shot, and repeating schedules.
-
commit
def cron(cronExpression string, name string = "") !ScheduleIdAdds a schedule that fires on the cron expression and returns its id.
-
commit
def runAt(timestamp time.Time, name string = "") !ScheduleIdAdds a schedule that fires once at the given time and returns its id.
The time must be in the future.
-
commit
def runAfter(dur duration, name string = "") !ScheduleIdAdds a schedule that fires once after the duration and returns its id.
The duration must be positive.
-
commit
def runEvery(interval duration, name string = "") !ScheduleIdAdds a schedule that fires every interval and returns its id.
The interval must be positive.
-
commit
def cancel(id ScheduleId) !noneRemoves the schedule.
Fails when there is no such schedule.
-
commit
def pause(id ScheduleId) !nonePauses the schedule.
Its timing is untouched; firings that fall due while it is paused are skipped.
Fails when there is no such schedule.
-
commit
def resume(id ScheduleId) !noneResumes 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.
-
query
def list() Schedule[]Every schedule.
-
query
def get(id ScheduleId) ?ScheduleThe schedule with that id, or none.
-
query
def next(id ScheduleId) ?time.TimeWhen the schedule next fires; none when there is no such schedule or it is paused.
-
emit
def trigger(id ScheduleId) ScheduleIdEmitted 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.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
-
commit
def open(path Path, mode string) !intOpen a file or folder at the given path with the specified mode.
-
emit
def fileOpened(path string) stringEmitted after a file is opened.
-
commit
def close(fd int) !noneCloses an open file handle.
-
commit
def write(fd int, data byte[]) !intWrites bytes at the handle's current position and returns how many were written.
-
commit
def read(fd int, data byte[]) !intReads into the buffer from the handle's current position and returns how many bytes were read.
-
commit
def readFile(path Path) !byte[]Reads a whole file into memory.
-
commit
def writeFile(path Path, data byte[]) !noneWrites the bytes to a file, replacing any existing content.
-
commit
def seek(fd int, offset int64, whence int) !int64Moves the handle's position and returns the new absolute offset.
Whence follows the Whence enum: start, current, or end.
-
commit
def readAt(fd int, data byte[], offset int64) !intReads into the buffer starting at an absolute offset, leaving the handle's position unchanged.
-
commit
def writeAt(fd int, data byte[], offset int64) !intWrites bytes at an absolute offset, leaving the handle's position unchanged.
-
commit
def truncate(fd int, size int64) !noneResizes the file to the given size, padding with zero bytes when it grows.
-
query
def sync(fd int, dataOnly bool = false) !nonePushes 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.
-
query
def lock(fd int, exclusive bool = true, wait bool = true) !boolTakes 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.
-
query
def unlock(fd int) !noneGives up the lock
locktook. -
commit
def memoryFile(name string = "") !intOpens 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.
nameonly 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.
-
commit
def seal(fd int) !noneMakes 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.
-
commit
def mmap(fd int, offset int = 0, length int = 0, access MapAccess = MapAccess.Read) !intMaps 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.
-
commit
def mmapRead(handle int, offset int, length int) !byte[]The mapped bytes from offset.
Fails when the range leaves the mapping.
-
commit
def mmapWrite(handle int, offset int, data byte[]) !noneWrites into the mapping at offset.
Fails on a read-only mapping or a range that leaves it.
-
query
def mmapLength(handle int) intBytes in the mapping, 0 for an unknown handle.
-
commit
def mmapFlush(handle int) !nonePushes a shared mapping's changes to the file.
Fails when the handle is not a mapping.
-
commit
def munmap(handle int) !noneReleases the mapping.
Fails when the handle is not a mapping.
-
commit
def control(fd int, request int, data byte[]) !intSends 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.
-
commit
def control(fd int, request int, value int) !intSends 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.
-
query
def wait(fd int, timeout duration, write bool = false) !boolWaits until the descriptor is readable, or writable when
writeis 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.
-
commit
def exists(path Path) !boolReturns whether a file or directory exists at the path.
-
commit
def remove(path Path) !noneRemoves a file.
-
commit
def rename(oldPath Path, newPath Path) !noneRenames or moves a file or directory.
-
commit
def mkdir(path Path) !noneCreates a directory, including any missing parents.
-
commit
def rmdir(path Path) !noneRemoves an empty directory.
-
query
def 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.
-
commit
def delete(path Path) !noneDelete a file or a folder at the given path
-
query
def stat(path Path) !os.FileInfoReturns size, mode, timestamps and the file system's identity numbers (device, inode, owner, group, link count) for a path.
-
query
def homeDir() !stringReturns the user's home folder:
HOME, orUSERPROFILEon Windows.os.homeDir()is the same call.Fails when the environment names none.
-
query
def resolve(path string) !stringReturns 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. -
query
def readLink(path Path) !stringReturns 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.
-
query
def 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. -
commit
def chmod(file Path, mode int64) !noneChanges a file's permission bits.
-
commit
def touch(file Path, modified time.Time) !noneSets 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.
-
query
def metrics(fd ?int) !FileMetricsReturns the reads and writes (
reads,writes,bytesRead,bytesWritten) of the open filefd, or withfdnone those of every file since the service started: closed files,readFile,writeFileand mapped reads and writes included.Fails when
fdis not a file the Storage service has open. -
query
def freeSpace(disk Path) !int64The 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.
-
query
def totalSpace(disk Path) !int64The size in bytes of the disk that holds the path.
Fails when the policy denies the path or the operating system refuses.
-
query
def 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 arefreeSpaceandtotalSpaceof 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.
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. |
-
commit
def config(val TelemetryConfig) !noneApplies the settings. Needs the permission
adm.telemetry.send. -
commit
def consent(granted bool) !noneRecords 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. -
query
def consented() ?boolThe user's answer: none when they were never asked.
-
query
def installation() stringThe 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, unlessrequireConsentis off. -
query
def collecting(signal TelemetrySignal = TelemetrySignal.Events) boolWhether records of the signal are collected now: the service runs with an exporter, the signal is configured, and the user agreed unless
requireConsentis off. -
query
def session() stringA random id made each time the service starts.
-
commit
def set(key string, value string)Adds a value to everything sent from now on; an empty value takes the key away.
-
commit
def report(err error, fields map<string, any> = {})Sends an error with where it was raised, and the calling task's current span.
-
commit
def event(name string, properties map<string, any> = {})Sends something the user did.
-
query
def span(name string, attributes map<string, any> = {}, parent ?SpanContext = none) SpanBegins a timed operation and makes it the calling task's current span;
endon the result finishes it. The span is a child ofparent, and without one of the task's current span. ASpanhasset,event,error,end,contextandtraceparent. A parent read byparseTraceparentis another program's (SpanContext.remote), and the span begun under it records that asSpanRecord.parentRemote. -
query
def current() ?SpanThe 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.
-
commit
def record(span SpanRecord)Sends a finished span built by the caller.
-
query
def flush(timeout duration = 5s) !noneSends what waits and returns once every exporter took it.
-
query
def dropped() intHow many records were lost: written while
capacitywaited already, refused for good by a receiver, in a batch nothing could keep, or in a kept batch removed to stay withinspoolSize.
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).
-
query
def openWindow<T: component>(root T, cfg r.WindowConfig) !r.WindowOpens 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@persiststore (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: whilerunserves the windows, a call from another task is made on the windows' loop at its next pass, and returns once it ran there. -
query
def openDefaultWindow<T: component>(root T) !r.WindowOpens a window with the `UIConfig.defaultWindow` settings and root as the component it renders.
-
query
def closeWindow(win r.Window) !noneCloses the window. From any task, as
openWindow. -
query
def window(title string = "", name string = "") ?r.WindowThe 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.
-
query
def raiseWindow(win r.Window) !nonePuts a window above the others. From any task, as
openWindow. -
query
def setWindowTitle(win r.Window, title string) !noneSets a window's title. While a
Pageshown 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, asopenWindow. -
query
def setPageTitle(win r.Window, page component, title ?string) !noneDeclares the title of
page, shown inwin, 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. WhatPagecalls. From any task, asopenWindow. -
query
def setWindowIcon(win r.Window, icon image.Image) !noneSets a window's icon. From any task, as
openWindow. -
query
def windows() r.Window[]The open windows.
-
commit
def 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.
-
commit
async def run<T: component>(root T, cfg r.WindowConfig) !noneOpens 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.
-
commit
async def run<T: component>(root T) !noneOpens a window for `root` with the `UIConfig.defaultWindow` settings and returns when the user closes it.
-
commit
def 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.
-
commit
def pumpAll() !nonepumpEvents for every open window.
-
query
def 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.
-
query
def appearance() AppearanceThe platform's look settings: colour scheme, accent, contrast, motion, text size and fonts.
-
query
def theme() ThemeThe 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. -
query
def 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.
-
query
def display(win r.Window) !DisplayThe display the window is on: the one holding most of it.
Fails when the service is not running or the window is unknown.
-
emit
def displaysChange(displays Display[]) Display[]Emitted when a monitor is added or removed, or one changes resolution or scale.
-
emit
def appearanceChange(appearance Appearance) AppearanceEmitted when a look setting changes, such as the user switching to dark mode.
-
emit
def themeChange(theme Theme) ThemeEmitted when `setTheme` gave styles another theme.
-
query
def 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.
-
query
def icon(name string) ?canvas.PictureThe picture of the icon called `name` (`set/name` for the one of that set); none when no set holds it.
-
query
def showTray(icon image.Image, tooltip string = "", menu MenuEntry[] = []) !noneShows 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. -
query
def hideTray() !noneRemoves the tray icon. From any task, as
openWindow. -
emit
def closeRequested(request CloseRequest) CloseRequestEmitted 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.
-
emit
def windowStateChanged(window r.Window) r.WindowEmitted when a window is minimized, maximized, restored or made fullscreen.
-
emit
def trayActivated() boolEmitted when the tray icon itself is clicked. The SDL3 host reports menu lines only, so it does not emit it.
-
emit
def windowDisplayChange(evt WindowDisplayEvent) WindowDisplayEventEmitted when a window moves to another display, or its display changes scale.
-
emit
def click(evt ClickEvent) ClickEventEmitted for a click.
targetis the component under it, the window's root when no other component is there. -
emit
def mouseMove(evt MouseMoveEvent) MouseMoveEventEmitted when the pointer moves, resolved to the component under it.
-
emit
def mouseWheel(evt MouseWheelEvent) MouseWheelEventEmitted for a wheel or trackpad scroll.
-
emit
def resize(evt ResizeEvent) ResizeEventEmitted when a window is resized.
-
emit
def close(evt CloseEvent) CloseEventEmitted when the user asks to close a window.
-
emit
def keyDown(evt KeyEvent) KeyEventEmitted when a key goes down.
-
emit
def keyUp(evt KeyEvent) KeyEventEmitted when a key comes up.
-
emit
def textInput(evt TextInputEvent) TextInputEventEmitted 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.
-
commit
def put<T>(name string, value Secret<T>) !noneKeeps
valueas the secretname, replacing what was there. Fails when the vault is not started, the name is not one, or the file cannot be written. Needsadm.vault.write. -
commit
def 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 asT. Needsadm.vault.read. -
commit
def store<T>(name string, value T) !noneKeeps
valueundername, replacing what was there. Fails asputdoes. Needsadm.vault.write. -
commit
def load<T>(name string) !?TThe 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 asT. Needsadm.vault.read. -
commit
def remove(name string) !noneRemoves 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. Needsadm.vault.write. -
query
def has(name string) !boolWhether an entry called
nameis there. Fails when the vault is not started. Needsadm.vault.read. -
query
def id() !stringThe 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 (
Telemetrysends it asapp.installation.id,Featuresas the subject's id), so whoever receives both can join them. Names undervault.are the vault's own and cannot be written or removed. Fails when the vault is not started. Needsadm.vault.read. -
query
def list(prefix string = "") !string[]The names that start with
prefix, sorted; every name for"". Fails when the vault is not started. Needsadm.vault.read. -
commit
def generateKey(name string, kind KeyKind) !noneMakes a new key of
kind(Ed25519,EcdsaP256,EcdsaP384,Rsa2048,Rsa3072,X25519) and keeps it asname, replacing what was there. Fails asputdoes. Needsadm.vault.write. -
commit
def importKey(name string, key PrivateKey) !noneKeeps
keyasname, replacing what was there. Fails asputdoes. Needsadm.vault.write. -
commit
def removeKey(name string) !noneRemoves the key
name; nothing happens for a name that is not there. Fails asremovedoes. Needsadm.vault.write. -
query
def publicKey(name string) !PublicKeyThe public half of the key
name. Fails when the vault is not started ornameis not a key. Needsadm.vault.read. -
commit
def sign(name string, message byte[], hash Hash = Hash.SHA256) !byte[]Signs
messagewith the keyname: Ed25519 signs the message itself; ECDSA signs itshashdigest and gives the ASN.1 signature; RSA signs the digest with PKCS#1 v1.5. Fails when the vault is not started,nameis not a key, or the key's kind does not sign (X25519). Needsadm.vault.keys. -
commit
def decrypt(name string, ciphertext byte[], hash Hash = Hash.SHA256) !byte[]Decrypts
ciphertextwith the RSA keyname(OAEP overhash). Fails when the vault is not started,nameis not a key, the key is not RSA, or the ciphertext does not open. Needsadm.vault.keys. -
commit
def agree(name string, peer PublicKey) !byte[]The shared secret of the X25519 key
nameandpeer. Fails when the vault is not started,nameis not a key, or either key is not X25519. Needsadm.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.
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().
@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.
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.
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.
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.
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()
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.
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.
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.
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.
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.
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.
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.
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.
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 ***.
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.
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.
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.
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.
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.
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.
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.
use std.services.scheduler::(after)
@after(30s)
def warmUp() {
primeCaches()
}
@runEvery
Scheduler · targets functions
@runEvery(interval duration, name string = "")
Calls the function every interval.
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.
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.
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.
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.
use std.services.telemetry::(span)
@span()
def checkout(cart Cart) !Receipt {
return try charge(cart)
}