Typed bindings
Declare the state a document reads and the events it raises, and have both checked at load.
By default a document can read any host state name and raise any event. Declaring them makes the document strict: the contract is written at the top of the file, and loading refuses a document that breaks it.
Declaring the contract
host_state { volume: float, muted: bool, title: string };
events { set_volume(float), toggle_mute(), rename(string) };
let main = fn () {
Text { text: title }
};
host_statelists the names the document reads and their types.eventslists the events it may raise and their argument types.
What gets checked
When a strict document loads, Masque checks the document against its own declarations. An event("menu") that events doesn't list fails to load. The error names the cause and says what to write: events { menu(...) }.
Each module's events belong to that module. A shared file imported by two documents declares the union of what it raises, and each importing document declares only its own.
The host side
Register a handler for each declared event, and supply each declared field:
let config = RuntimeConfig::new()
.with_host_state(initial_state)
.with_event_handler("set_volume", |args| {
if let Some(Value::Float(v)) = args.first() {
audio.set_volume(*v);
}
true
});
The handler receives the event's arguments as &[Value] and returns whether it handled the event.
TODO: Rust-side derives for state and message types, so the host half of the contract is checked too. The design exists; the macros are not in the current tree.