Hail
A hail is a channel for one value. Rust holds the value, JS gets a signal that tracks it.
Read-only or read-write
Section titled “Read-only or read-write”Two constructors, and the choice decides what JS gets.
fn run_hail(key: Hail) -> JsValue { let state = use_context::<Stock<State>>().unwrap(); match key { // read-write: JS gets a value and a setter Hail::Count => state.count().set_hail::<Converter>(),
// read-only: JS gets a value Hail::Doubled => state.count().memo(|c| *c * 2).set_read_hail::<Converter>(), }}set_hail needs something writable. set_read_hail works on anything you can
read.
| You have | Can use |
|---|---|
Stock<T> |
both |
ReadStock<T> |
set_read_hail |
Memo<T> |
set_read_hail |
Resource<T> |
set_read_hail |
Like run_pier, run_hail runs once per key: when a component first asks for
it. After that the value is pushed.
Reading it
Section titled “Reading it”const pier = usePier();
const [count, setCount] = pier.hail("Count"); // () => numberconst doubled = pier.readHail("Doubled"); // () => number
setCount(count() + 1);const [count, setCount] = useHail("Count"); // numberconst doubled = useReadHail("Doubled"); // number
setCount(count + 1);const count = useHail("Count"); // WritableComputedRef<number>const doubled = useReadHail("Doubled"); // ComputedRef<number>
count.value++; // or v-model, or count++ in a templateconst count = useHail("Count"); // Writable<number>const doubled = useReadHail("Doubled"); // Readable<number>
$count += 1;Each adapter returns whatever is idiomatic for that framework. A Vue hail is a
writable ref, so v-model works on it directly. A Svelte hail is a store, so
$count works.
Keys can carry data
Section titled “Keys can carry data”A key is any value your enum can be. Give a variant a field and the key becomes an object.
#[derive(Rets, Serialize, Deserialize)]pub enum Hail { #[ret(Vec<i32>)] Items, #[ret(Option<i32>)] Item(usize),}pier.readHail("Items"); // number[]pier.readHail({ Item: 3 }); // number | undefinedThat is plain serde. No constructors, no wrapper objects. The key is the value you write.
These two keys read an items: Vec<i32> field on the app’s state, reached with
state.items(). Those accessors come from #[derive(Stock)]. See deriving
stocks when you get to the Rust side.
Values that may be absent
Section titled “Values that may be absent”Some values are not always there. An index past the end of a Vec, a missing
map key, a field on the wrong enum variant.
Those become OptStock<T> on the Rust side and undefined on the JS side, so
declare the ret as Option<T>:
#[ret(Option<i32>)]Item(usize),Hail::Item(index) => state.items().get(index).set_hail::<Converter>(),Writing to an absent path is simply ignored. It does not panic.
Try it
Section titled “Try it”Items is a plain list. Item(0) is derived from it: writable, and
undefined once the list is empty.
Push and pop go through tells; the +1 button writes straight into
the derived path.
import { usePier } from "../../setup/solid/bridge";
export default function Items() { const pier = usePier(); const items = pier.readHail("Items"); // () => number[] const [first, setFirst] = pier.hail({ Item: 0 }); // path-derived, writable
return ( <div class="demo"> <p> items: <b id="items">{items().join(", ") || "(empty)"}</b> </p> <p> item 0: <b id="item0">{first() ?? "undefined"}</b> </p> <button id="push" onClick={() => pier.tell({ PushItem: items().length * 10 })}> push </button> <button id="pop" onClick={() => pier.tell("PopItem")}> pop </button> <button id="bump" onClick={() => setFirst((first() ?? 0) + 1)}> +1 on item 0 </button> </div> );}import { useHail, useReadHail, useTell } from "../../setup/react/bridge";
export default function Items() { const items = useReadHail("Items"); // number[] const [first, setFirst] = useHail({ Item: 0 }); // path-derived, writable const tell = useTell();
return ( <div className="demo"> <p> items: <b id="items">{items.join(", ") || "(empty)"}</b> </p> <p> item 0: <b id="item0">{first ?? "undefined"}</b> </p> <button id="push" onClick={() => tell({ PushItem: items.length * 10 })}> push </button> <button id="pop" onClick={() => tell("PopItem")}> pop </button> <button id="bump" onClick={() => setFirst((first ?? 0) + 1)}> +1 on item 0 </button> </div> );}<script setup lang="ts">import { useHail, useReadHail, useTell } from "../../setup/vue/bridge";
const items = useReadHail("Items"); // ComputedRef<number[]>const first = useHail({ Item: 0 }); // path-derived, writableconst tell = useTell();</script>
<template> <div class="demo"> <p> items: <b id="items">{{ items.join(", ") || "(empty)" }}</b> </p> <p> item 0: <b id="item0">{{ first ?? "undefined" }}</b> </p> <button id="push" @click="tell({ PushItem: items.length * 10 })">push</button> <button id="pop" @click="tell('PopItem')">pop</button> <button id="bump" @click="first = (first ?? 0) + 1">+1 on item 0</button> </div></template><script lang="ts"> import { useHail, useReadHail, useTell } from "../../setup/svelte/bridge";
const items = useReadHail("Items"); // Readable<number[]> const first = useHail({ Item: 0 }); // path-derived, writable const tell = useTell();</script>
<div class="demo"> <p> items: <b id="items">{$items.join(", ") || "(empty)"}</b> </p> <p> item 0: <b id="item0">{$first ?? "undefined"}</b> </p> <button id="push" on:click={() => tell({ PushItem: $items.length * 10 })}>push</button> <button id="pop" on:click={() => tell("PopItem")}>pop</button> <button id="bump" on:click={() => ($first = ($first ?? 0) + 1)}>+1 on item 0</button></div>Running now, in your browser
Watch what happens when you pop the list empty: item 0 becomes undefined
rather than throwing, and +1 on it does nothing.
Writes are precise
Section titled “Writes are precise”Ahoi tracks which value each hail actually read. When you write, only the hails that depend on that value are recomputed.
Derivation is path-selective. Writing items[0] notifies subscribers of
that path and of its ancestors, but not of items[1].
One dispatch per change
Section titled “One dispatch per change”Recomputed hails are collected and sent to JS in a single batch per propagation cycle.
One write that touches twenty hails costs one crossing, not twenty. You do not need to batch anything by hand.
Hails are how values come out. Tells are how commands go in.