myrmic_sdk/lib.rs
1//! The SDK for writing Myrmic cells: WebAssembly modules that deploy and
2//! self-organize across a swarm of devices.
3//!
4//! Myrmic runs application logic as *cells* — portable, isolated Wasm units
5//! the runtime deploys, supervises, and connects across embedded, mobile, and
6//! server targets. This crate is what a cell links against: it turns plain
7//! Rust functions into the exports the runtime invokes, and wraps everything
8//! the host offers in safe Rust APIs.
9//!
10//! # A minimal cell
11//!
12//! A cell is a `no_std` library crate. There is no `main` and no allocator or
13//! panic-handler boilerplate — the default `cell-init` feature emits those via
14//! [`cell_prelude!`]. Handlers are free functions marked with the attributes
15//! below:
16//!
17#![doc = concat!(
18 "```ignore\n",
19 include_str!("doc_examples/counter.rs"),
20 "```"
21)]
22//!
23//! The [`tests/fixtures/cell-*`](https://github.com/peeriot/myrmic/tree/master/tests/fixtures)
24//! crates are complete working cells exercising each feature; they are the
25//! best starting templates.
26//!
27//! # Handlers
28//!
29//! | Attribute | Export invoked when | Payload |
30//! |---|---|---|
31//! | [`#[init]`](macro@init) | Once per incarnation: first deploy, and again on every respawn/restart | optional spawn argument |
32//! | [`#[cmd]`](macro@cmd) | Another cell [`send`]s the command (or a timer tick / gateway call names it) | any [`Decoder`]; replies go through a [`Callback`] parameter |
33//! | [`#[evt]`](macro@evt) | A subscribed event is [`publish`]ed | the event's payload |
34//! | [`#[monitor]`](macro@monitor) | A child cell dies (crash, stop, termination, node loss) | [`monitor::CellLost`] |
35//!
36//! Every handler takes a leading [`Metadata`] (the cell's own [`Sri`] and the
37//! sender's) and returns [`Result`]. Payload types derive [`Message`] to pick
38//! their wire codec ([`Json`] by default, or any [`Codec`] via
39//! `#[codec(...)]`).
40//!
41//! # What the host offers
42//!
43//! - **Messaging** — [`send`] commands to a cell, [`publish`] events to
44//! subscribers, reply via [`Callback`].
45//! - **Storage** (the *datalayer*, module [`db`]) — typed handles
46//! [`Kv`](db::tree::Kv), [`Table`](db::table::Table),
47//! [`State`](db::state::State), plus blob, time-series, and semantic
48//! stores. Private db state survives restarts and respawns.
49//! - **Spawning & supervision** — [`declare!`] a child class, spawn it with
50//! [`ClassHandle`], get [`monitor`](macro@monitor) callbacks when it dies,
51//! [`terminate_cell`] / [`stop_self`] to tear down.
52//! - **Timers** — [`delay`], [`interval`], [`interval_at`] schedule future
53//! invocations of a named command export.
54//! - **Time** — [`now`] (swarm-synchronised wall clock), [`uptime`]
55//! (monotonic), [`wait`].
56//! - **Logging** — [`trace!`] … [`error!`] macros and their `_str` variants.
57//! - **Signal layer** — [`tap`](mod@tap)s onto host-side signals, [`gpio`]
58//! pins, [`ble`] centrals/peripherals.
59//! - **Bridges** — [`import!`](macro@import) generates typed clients for
60//! HTTP APIs and MQTT brokers from YAML specs.
61//! - **Gateway** — [`gateway`] mounts blob scopes as static assets and routes
62//! HTTP to commands.
63//!
64//! # Execution model
65//!
66//! Cells are single-threaded: the runtime invokes one export at a time, so
67//! handlers never race each other. A panic is logged with its location and
68//! traps the module; the parent (if any) hears about it through its
69//! [`monitor`](macro@monitor) handler. Durable state belongs in the
70//! datalayer — volatile resources like timers die with the incarnation and
71//! are re-established in [`#[init]`](macro@init).
72//!
73//! # Feature flags
74//!
75//! | Feature | Default | Enables |
76//! |---|---|---|
77//! | `alloc` | yes | heap, codecs, everything payload-shaped |
78//! | `cells` | yes | messaging, spawning, timers, handler attributes |
79//! | `db` | yes | the datalayer APIs |
80//! | `cell-init` | yes | the allocator/panic-handler prelude a deployable cell needs |
81//! | `eio` | no | `embedded-io` codecs for the shared wire types |
82//! | `types-web` | no | `myrmic-common`'s web wire types, for a build that takes that crate without its defaults |
83//!
84//! The default set is the only configuration that builds today: without
85//! `alloc` the mandatory `serde_json` dependency has no allocator, and
86//! `myrmic-common` is taken with its own defaults, so the shared `cells`,
87//! `db` and `types-web` items - [`types::web`] among them - are present no
88//! matter what is selected above.
89//!
90//! # Further reading
91//!
92//! The [Myrmic book](https://book.myrmic.dev/) carries the quickstart,
93//! tutorials (observability, BLE), and architecture chapters; this crate's
94//! docs are the API reference.
95
96#![no_std]
97#![cfg_attr(
98 all(feature = "alloc", target_arch = "wasm32"),
99 feature(alloc_error_handler)
100)]
101#![warn(missing_docs)]
102#![allow(clippy::cast_possible_truncation)] // using the crate just from Wasm -> no need to worry about casting to i32
103#![allow(clippy::cast_possible_wrap)] // using the crate just from Wasm -> no need to worry about casting to i32
104#![allow(clippy::pedantic)] // temporary: disable pedantic checks for sdk crate
105#![allow(clippy::unsafe_derive_deserialize)] // temporary: accepted in current sdk types
106#![allow(clippy::new_without_default)] // temporary: whitelist pedantic lint noise in sdk API surface
107#![allow(clippy::must_use_candidate)] // temporary: whitelist pedantic lint noise in sdk API surface
108#![allow(clippy::missing_errors_doc)] // temporary: whitelist pedantic lint noise in sdk API docs
109
110#[cfg(feature = "alloc")]
111extern crate alloc;
112#[cfg(feature = "alloc")]
113pub use alloc::{format, string::String, vec, vec::Vec};
114
115/// The raw payload buffer type the [`Decoder`]/[`Encoder`] traits work in terms of.
116#[cfg(feature = "alloc")]
117pub type Bytes = Vec<u8>;
118
119#[cfg(feature = "alloc")]
120pub use codec::{Codec, Decoder, Encoder, Json, Postcard, Void};
121
122#[cfg(feature = "alloc")]
123pub use serde_json::Value as JsonValue;
124
125#[cfg(feature = "alloc")]
126mod allocation;
127#[cfg(feature = "alloc")]
128mod codec;
129mod error;
130mod host_functions;
131#[cfg(feature = "cells")]
132mod messages;
133#[cfg(feature = "cells")]
134mod messaging;
135mod metadata;
136mod panic_handlers;
137
138#[cfg(feature = "cells")]
139pub use messages::{Callback, Handler};
140
141#[cfg(feature = "cells")]
142pub use messaging::{publish, send};
143
144pub use metadata::{Metadata, Sri};
145
146#[cfg(feature = "cells")]
147mod traits;
148
149pub use myrmic_common::signal_layer;
150pub use myrmic_common::types;
151
152#[cfg(feature = "cells")]
153pub use myrmic_common::cells::{Command, Event, EventPublishRequest};
154
155/// Everything a supervising parent needs: the [`monitor`](macro@crate::monitor)
156/// handler's payload types and [`stop_self`] for escalation.
157///
158/// ```ignore
159/// use myrmic_sdk::monitor::{CellLost, LostReason};
160///
161/// #[myrmic_sdk::monitor]
162/// fn lost(md: myrmic_sdk::Metadata, l: CellLost) -> myrmic_sdk::Result<()> {
163/// // respawn / escalate / ignore
164/// Ok(())
165/// }
166/// ```
167pub mod monitor {
168 pub use myrmic_common::cells::{CellLost, LostReason};
169
170 pub use crate::host_functions::stop_self;
171
172 use crate::{Decoder, Postcard, Result};
173
174 impl Decoder for CellLost {
175 fn from_bytes(bytes: crate::Bytes) -> Result<Self> {
176 <Postcard as crate::Codec>::decode(&bytes)
177 }
178 }
179}
180
181#[cfg(feature = "cells")]
182pub use host_functions::{
183 ClassHandle, ClassRef, CommandError, InMemory, SpawnBuilder, SpawnError, SpawnRequest,
184 TerminateError, TimerHandle, delay, interval, interval_at, publish_event, spawn_cell,
185 stop_self, terminate_cell,
186};
187
188/// Declares a reference to a child cell class by name, returning a
189/// [`ClassHandle`] to spawn it with.
190///
191/// The name is resolved to the child's content hash at deploy time and patched
192/// into the module; the reference is stable across redeploys and independent of
193/// how the child class is registered. Bind it once and reuse it:
194///
195/// ```
196/// # fn demo(i: u32) -> Result<(), myrmic_sdk::SpawnError> {
197/// const CHILD: myrmic_sdk::ClassHandle = myrmic_sdk::declare!("child");
198/// CHILD.new().name(format!("child-{i}")).spawn()?;
199/// # Ok(())
200/// # }
201/// ```
202#[cfg(feature = "cells")]
203#[macro_export]
204macro_rules! declare {
205 ($name:literal) => {{
206 #[used]
207 static __SPAWN_REF: $crate::__private::SpawnRef<{ $name.len() }> =
208 $crate::__private::SpawnRef::new($name);
209 $crate::ClassHandle::from_hash_ref(__SPAWN_REF.hash_ref())
210 }};
211}
212
213/// Support items referenced by exported macros; not a stable API.
214#[doc(hidden)]
215#[cfg(feature = "cells")]
216pub mod __private {
217 pub use myrmic_common::cells::spawn_ref::SpawnRef;
218}
219
220#[cfg(feature = "cells")]
221pub use traits::CellEvent;
222
223#[cfg(feature = "cells")]
224pub use host_functions::ble;
225#[cfg(feature = "cells")]
226pub use host_functions::ble::{
227 Address, Advertisement, Characteristic, DiscoveredDevice, DiscoveryFilter, ManufacturerData,
228 NotifyError, ReadError, ScanMode, Service, ServiceData, Uuid, WriteError, mac_addr_pub,
229 mac_addr_rand, uuid128,
230};
231#[cfg(all(feature = "alloc", feature = "db"))]
232pub use host_functions::db;
233#[cfg(all(feature = "cells", feature = "db"))]
234pub use host_functions::gateway;
235pub use host_functions::{
236 LogLevel, Outlet, RawBuf, Tap, TapKind, debug_str, error_str, get_arguments, gpio, info_str,
237 list_entry, list_len, log, log_buffer, now, outlet, report_error, tap, trace_str, uptime, wait,
238 warn_str,
239};
240#[cfg(feature = "alloc")]
241pub use host_functions::{runtime_id, runtime_tags};
242pub use signal_layer_types::WireType;
243
244pub use error::{ApiError, ApiResult, Result};
245pub use myrmic_common::types::error::*;
246
247/// Re-exported so that `import_cell!`-generated types can derive `Serialize`/`Deserialize`
248/// without the consumer crate needing a direct `serde` dependency.
249pub use serde;
250
251/// The handler-export attributes, usable as `#[myrmic_sdk::cmd]` etc.
252pub use myrmic_sdk_macros::{cmd, evt, import, init, monitor};
253
254/// Derives for the [`Codec`]-backed `Decoder`/`Encoder`
255/// impls, usable as `#[derive(myrmic_sdk::Message)]`.
256pub use myrmic_sdk_macros::Message;
257
258#[cfg(feature = "alloc")]
259pub use allocation::__DefaultHeap;
260
261#[doc(hidden)]
262pub use panic_handlers::{__DEFAULT_HEAP_SIZE, __parse_usize};
263
264// The prelude emits a `#[panic_handler]` and the wasm allocator, which only
265// compile for the wasm32 cell target.
266#[cfg(all(feature = "cell-init", target_arch = "wasm32"))]
267cell_prelude!();
268
269// Re-export so the macros can refer to embedded-alloc via `$crate::...`
270// and the *consumer* crate doesn't need to depend on the deps of the sdk crate.
271#[doc(hidden)]
272pub mod __reexports {
273 pub use critical_section;
274 #[cfg(feature = "alloc")]
275 pub use embedded_alloc;
276 pub use embedded_hal;
277 pub use spin;
278}
279
280/// Re-exports the `import!` macro's generated code depends on. Not public API.
281#[cfg(feature = "alloc")]
282#[doc(hidden)]
283pub mod codegen;