MoonBit application framework without a user interface
Hosting | @lander, @hosting: services, lifecycle, exit codes (first slice)
Binding | @config.bind: typed values with source-aware errors
Conventions | @conventions: where configuration files are found
Configuration core | @config: sources, file tree, pure load, merge
Formats | TOML, YAML, JSON5 and Ion; linked set selected by a virtual package| Layer | Package | Status |
|---|---|---|
| Configuration core | moonrockz/moon-lander-config | Value tree, key paths, sources (defaults, files, environment, overrides) and the builder, file tree and the pure load, merge with null rules, provenance, merge policies, lazy secrets, redaction, display helpers and typed errors |
| Formats | formats, parsers/* in moonrockz/moon-lander-config | TOML, YAML with default/strict profiles, JSON/JSON5 and Ion; default and TOML-only linked sets |
| Host adapters | host in moonrockz/moon-lander-config | native and js read files and fill missing variables and the working directory |
| Browser adapter | browser in moonrockz/moon-lander-config | js browser URL mounts with explicit manifests, run inputs, limits, cancellation, and typed boundary errors |
| Conventions | conventions in moonrockz/moon-lander-config | Walk-up discovery in three layouts, system and global user directories on every platform, local and user overrides, explicit project file, ambiguity, legacy fallback, stop boundary, isolated mode; workspace and environment parts come next |
| Binding | moonrockz/moon-lander-config | bind, bind_root, and bind_or over a default, with source-aware decode errors; validation comes next |
| Mediator | moonrockz/moon-lander-mediator | Typed senders, publish topics that run every handler and report every failure in one PublishFailed, a middleware pipeline with fixed order and short-circuit, an explicit context value and MediatorError; one synchronous API on all four targets |
| Async mediator | moonrockz/moon-lander-mediator/async | Async senders and topics with sequential and concurrent publish, caller cancellation, the timeout middleware, async middleware with a sync finally_, and from_sync for core values; wasm, js and native |
| Logging | moonrockz/moon-lander-logging (@logging) | The bring-your-own Logger trait, six levels, structured events with Serilog-style message templates, a level check before each event, the null and in-memory loggers, min_level, fan_out and with_category, and text lines; one API on all four targets |
| Hosting | moonrockz/moon-lander-hosting (@hosting), @lander.cli | First slice: a builder with hosted services that have start and stop hooks, a services factory, and @hosting.run on native and js, which gives exit code 0 or 1. Arguments, lifecycle events, signals and configuration come next |
import {
"moonrockz/moon-lander-config" @config,
"moonrockz/moon-lander-config/parsers/toml" @toml,
}| Step | What it does |
|---|---|
| @lander.cli(name, args) | Starts the builder with an AppName and the arguments |
| @hosting.service(name, start=.., stop=..) | Adds a hosted service. name is a ServiceName or a String that the step parses. Both hooks are optional and can wait. A second service whose name equals another or differs from it only in letter case raises DuplicateService |
| @hosting.build(factory) | Gives the Host. It does no I/O, so a host can run more than one time |
| Type | Parse | Rule |
|---|---|---|
| AppName | AppName::parse("acme") | Starts with an ASCII letter; then only ASCII letters, digits, - and _ |
| ServiceName | ServiceName::parse("db") | Safe as one file path component: starts with a Unicode identifier start character (XID_Start, letters and letter numbers); then identifier characters (XID_Continue: letters, combining marks, digits, _ and a few connectors), - and .; no invisible character; does not end with .; is not a Windows device name such as CON or com1; at most 128 bytes in UTF-8 |
| ExitCode | ExitCode::from_int(3), or ExitCode::success() and ExitCode::failure() | 0 to 255 |
///|
/// The host of the application `acme`. Its one service writes each hook call
/// into `log`.
fn acme_host(log : Array[String]) -> @hosting.Host[Unit] raise {
"acme"
|> @lander.AppName::parse
|> @lander.cli([])
|> @hosting.service(
"greeter",
start=_ => log.push("greeter started"),
stop=_ => log.push("greeter stopped"),
)
|> @hosting.build(_ => ())
}
///|
test "build the acme host" {
let log = []
ignore(acme_host(log))
// The build runs no hook.
assert_eq(log, [])
}| Result | Exit code |
|---|---|
| All hooks and the command succeed | 0 |
| The factory, a hook or the command raises an error | 1, and failures holds each error |
| An argument that the command does not define | 2, UsageError |
///|
test "explicit starting settings" {
let host = @lander_core.AppName::parse("acme")
|> @lander.cli([], env_vars={ "ACME_PORT": "8080" },
working_dir=@lander_core.AbsolutePath::parse("/work"), environment=@lander.EnvironmentName::parse("Staging"))
|> @hosting.env_vars(({ "ACME_PORT": 9090, "ACME_DEBUG": true } : Json))
|> @hosting.working_dir(@lander_core.AbsolutePath::parse("/other"))
|> @hosting.build(_ => ())
ignore(host)
}fn cli(name : AppName, args : Array[String], env_vars? : &IntoEnvVars, working_dir? : AbsolutePath, environment? : EnvironmentName) -> HostBuilder[Unit]