Macula SDK

LicenseErlang/OTPHex.pmBuy Me A Coffee

Macula

Erlang/OTP client SDK for the Macula HTTP/3 mesh


Since 10.5.0: every supervised primitive pair is complete and symmetric, each wrapping its raw SDK primitive as an OTP behaviour with a simple_one_for_one factory supervisor, mesh-visible protocol facts (sharing.*_v1, streaming.*_v1, rpc.*_v1) around its own side of the operation, and both a pooled and a direct-dial (resolve + one-hop dial) mode:

All additive since 9.2.0, no breaking changes. See CHANGELOG.md for the full version-by-version history.

What is Macula?

Macula SDK Component and Feature Model

Macula is an Erlang/OTP client SDK for building applications on a mesh of stations — realm-agnostic relays that route over QUIC (HTTP/3) and form a Kademlia DHT. Your service or daemon connects outbound to one or more stations: no open ports, NAT-friendly, no VPN. It provides:

The station (routing, DHT, SWIM, peering) is a separate repo, macula-station; this package is the client you build against.


Quick Start

Add to rebar.config:

{deps, [{macula, "~> 10.5"}]}.

Or in Elixir mix.exs:

defp deps do
[{:macula, "~> 10.5"}]
end

SDK Connect Flow

application:ensure_all_started(macula),
%% Connect a pool to one or more stations (seed URLs). The pool owns one
%% QUIC link per seed, reconnecting and replaying subscriptions as needed.
{ok, Pool} = macula:connect([<<"quic://boot.macula.io:443">>], #{}),
%% A realm is a 32-byte tag derived from a name; it scopes every call.
%% Keep the name around too — topics are built from it, not the tag.
RealmName = <<"io.example.myapp">>,
Realm = macula_realm:id(RealmName),
%% Topics/procedures are built via macula_topic, never hand-typed — a
%% typo becomes a wrong VALUE your own tests catch, not two strings
%% silently drifting apart. Facts (pub/sub) are past tense; hopes (RPC)
%% are present tense. See docs/guides/shared/TOPIC_NAMING_GUIDE.md.
Topic = macula_topic:app_fact(RealmName, <<"example">>, <<"myapp">>,
<<"sensors">>, <<"temperature_measured">>, 1),
Procedure = macula_topic:app_hope(RealmName, <<"example">>, <<"myapp">>,
<<"math">>, <<"add">>, 1),
%% Subscribe (delivers {macula_event, Ref, Topic, Payload, Meta} to a pid),
{ok, Ref} = macula:subscribe(Pool, Realm, Topic, self()),
%% or subscribe with a callback fun(Topic, Payload, Meta):
{ok, Ref2} = macula:subscribe_callback(
Pool, Realm, Topic,
fun(_Topic, Payload, _Meta) -> io:format("~p~n", [Payload]) end),
%% Publish. Entity IDs go in the PAYLOAD, never in the topic.
ok = macula:publish(Pool, Realm, Topic,
#{sensor => <<"kitchen">>, value => 23.5}),
%% Advertise an RPC procedure (open to any identified caller here),
ok = macula:advertise(Pool, Realm, Procedure,
fun(#{<<"a">> := A, <<"b">> := B}) -> {ok, A + B} end,
#{}),
%% Call it — the SDK resolves the provider and dials its station directly.
{ok, 5} = macula:call(Pool, Realm, Procedure,
#{<<"a">> => 2, <<"b">> => 3}, 5_000).

Identity and Crypto (NIF-accelerated)

Identity and Crypto Stack

Rust NIFs with pure-Erlang fallbacks:

%% Ed25519 keypair (a #{public := _, private := _} map)
KP = macula_identity:generate(),
Sig = macula_identity:sign(<<"hello">>, KP),
true = macula_identity:verify(<<"hello">>, Sig, macula_identity:public(KP)),
%% BLAKE3 hashing
Hash = macula_blake3_nif:hash(<<"hello">>),
%% UCAN capability tokens — Issuer/Audience are DIDs (binaries), not raw keys
Issuer = <<"did:macula:io.example.myapp">>,
Audience = <<"did:macula:io.example.otherapp">>,
Caps = [#{with => <<"mri:sensor:io.example.myapp/kitchen">>,
can => <<"read">>}],
{ok, Token} = macula_ucan_nif:create(Issuer, Audience, Caps,
macula_identity:private(KP)),
{ok, Payload} = macula_ucan_nif:verify(Token, macula_identity:public(KP)).

Documentation

GuideDescription
ConnectingPools, seeds, TLS policy, reconnection
PubSub GuideFan-out + per-publisher delivery ordering
PubSub ProtocolRaw subscribe/publish primitives
Topic NamingEvent-type topics, IDs in payloads
RPC GuideDirect-dial request/response
RPC ProtocolRaw advertise/call primitives, error codes
Content GuideContent-addressed blobs (MCID), push/upload
Content ProtocolRaw put_content/get_content, MCID format, discovery
Records GuideSigned, TTL'd facts in the DHT — your own record types
Streaming GuideStreaming RPC (server / client / bidi)
Streaming ProtocolRaw call_stream/advertise_stream primitives
HyParView GuideBounded partial-view realm membership
Plumtree GuideEpidemic broadcast trees, realm PubSub, OR-Set CRDT
Distribution Over MeshErlang dist through the mesh
ClusteringLAN gossip clustering
AuthorizationDID / UCAN / cert-chain trust
MRI GuideResource identifiers
DevelopmentBuilding and testing
GlossaryTerminology

The station server lives in macula-station.


ProjectDescription
macula-stationThe station: DHT, SWIM, routing, peering
macula-realmManaged-realm identity + certificate authority
macula-mri-khepriDistributed MRI persistence (Khepri/Raft)
macula-ecosystemDocumentation hub

License

Apache 2.0 — see LICENSE.


Built with the BEAM