macula (macula v3.13.0)
View SourceMacula SDK — Public API for mesh applications.
This is the main entry point for applications using the Macula SDK.
V2 (canonical, since 3.11.0)
Apps connect via connect/2, which returns a macula_client pool that internally wraps N peering links to N stations. publish/4, publish/5, subscribe/4, subscribe/5, and unsubscribe/2 route through the pool with realm-per-call semantics. See macula_pubsub for the slice module of the same surface.
V1 (legacy, retired at 4.0.0)
Older apps used a single-connection client (macula_mesh_client). V1 facade surfaces still here untouched: subscribe/3, publish/3, call/3,4, advertise/3,4, unadvertise/2, put_record/2, plus all stream and directed-RPC operations. V1 callers wishing to keep V1 semantics for connect/publish/4/ unsubscribe/close after upgrading to 3.11.0 should call macula_mesh_client / macula_stream_v1 directly — see the migration guide.
Summary
Types
V1 client (macula_mesh_client).
V2 pool (macula_client).
V2 32-byte realm tag.
DHT storage key — macula_record:storage_key/1 output.
Functions
Abort the stream with an error frame.
Advertise an RPC procedure handler.
Advertise with options.
Advertise a streaming procedure (default: server_stream). LOCAL dispatch only — see advertise_stream/3,4 for the Client form.
Advertise a streaming procedure.
Advertise with explicit mode / options.
Wait for the terminal reply (client-stream / bidi).
Call a remote procedure (default 5s timeout).
Call a remote procedure with timeout.
Call a remote procedure on a specific target node (default 5s timeout).
Call a remote procedure on a specific target node with timeout.
Open a server-stream call (default mode).
Open a server-stream call with options.
Open a remote server-stream call against a mesh client, with opts.
OTP child spec to drop a V2 pool into a caller's supervision tree.
Stop a V2 pool. Every subscriber receives a final {macula_event_gone, SubRef, pool_closed} message.
Half-close the write side; recv still drains.
Connect to the Macula relay mesh and return a pool handle.
Disconnect a V1 macula_mesh_client client. **Legacy** — V2 pools use close/1.
Ensure this node is running in distributed mode.
Fetch a record by its macula_record:storage_key/1.
Return every record of a given type currently visible from the connected relay.
Get the Erlang cluster cookie.
Enable Erlang distribution over a dedicated dist relay (macula-io/macula-dist-relay).
Join the Macula relay mesh with Erlang distribution.
List all nodes connected to the relay (default 5s timeout).
List all nodes connected to the relay with options.
Subscribe to node up/down events.
Open a client-stream or bidi call.
Open a stream with explicit mode.
Publish to (Realm, Topic) on Pool. Equivalent to publish/5 with empty opts.
Publish to (Realm, Topic) on Pool with options. See macula_pubsub:publish/5 for honored opts.
Store a signed record in the mesh DHT.
Receive the next chunk (blocks).
Resolve a mesh name to node identity information.
Send a binary chunk on the stream.
Send a chunk with explicit encoding.
Set the Erlang cluster cookie.
Server-side: emit the terminal reply value.
V1 subscribe — single-connection client, no realm. **Legacy**. V2 callers use subscribe/4 or subscribe/5.
Subscribe Subscriber to (Realm, Topic) on Pool. Equivalent to subscribe/5 with empty opts.
Subscribe Subscriber to (Realm, Topic) on Pool with options. See macula_pubsub:subscribe/5.
Subscribe to live record-stored events filtered by type.
Stop advertising a procedure.
Stop advertising a streaming procedure.
Unsubscribe from node up/down events.
Drop a V2 pool subscription. Idempotent.
Cancel a subscribe_records/3 subscription.
Types
-type client() :: pid().
V1 client (macula_mesh_client).
-type pool() :: macula_client:pool().
V2 pool (macula_client).
-type procedure() :: binary().
-type realm() :: <<_:256>>.
V2 32-byte realm tag.
-type record() :: macula_record:record().
-type record_key() :: <<_:256>>.
DHT storage key — macula_record:storage_key/1 output.
-type record_type() :: macula_record:type_tag().
-type stream() :: pid().
-type stream_mode() :: server_stream | client_stream | bidi.
-type topic() :: binary().
Functions
Abort the stream with an error frame.
Advertise an RPC procedure handler.
Advertise with options.
-spec advertise_stream(procedure(), stream_handler()) -> ok | {error, term()}.
Advertise a streaming procedure (default: server_stream). LOCAL dispatch only — see advertise_stream/3,4 for the Client form.
-spec advertise_stream(procedure() | client(), stream_mode() | procedure(), stream_handler()) -> ok | {error, term()}.
Advertise a streaming procedure.
Two shapes: advertise_stream(Procedure, Mode, Handler) — LOCAL dispatch advertise_stream(Client, Procedure, Handler) — REMOTE, default mode server_stream
-spec advertise_stream(procedure() | client(), stream_mode() | procedure(), stream_handler() | stream_mode(), map() | stream_handler()) -> ok | {error, term()}.
Advertise with explicit mode / options.
Two shapes: advertise_stream(Procedure, Mode, Handler, Opts) — LOCAL advertise_stream(Client, Procedure, Mode, Handler) — REMOTE
Wait for the terminal reply (client-stream / bidi).
Call a remote procedure (default 5s timeout).
Call a remote procedure with timeout.
Call a remote procedure on a specific target node (default 5s timeout).
Target can be a mesh name, site_id, or node_id (all binaries). The relay resolves the target and routes the CALL directly to that node.
-spec call_node(client(), binary(), procedure(), term(), pos_integer()) -> {ok, term()} | {error, term()}.
Call a remote procedure on a specific target node with timeout.
Open a server-stream call (default mode).
Phase 1 shortcut — dispatches in-process via the local registry. For cross-node streaming use the (Client, Procedure, Args) form.
-spec call_stream(procedure() | client(), procedure() | term(), term() | map()) -> {ok, stream()} | {error, term()}.
Open a server-stream call with options.
Two shapes: call_stream(Procedure, Args, Opts) — LOCAL dispatch only call_stream(Client, Procedure, Args) — REMOTE via mesh client
Open a remote server-stream call against a mesh client, with opts.
-spec child_spec(term(), [macula_client:seed()], macula_client:opts()) -> supervisor:child_spec().
OTP child spec to drop a V2 pool into a caller's supervision tree.
-spec close(pool()) -> ok.
Stop a V2 pool. Every subscriber receives a final {macula_event_gone, SubRef, pool_closed} message.
-spec close_send(stream()) -> ok.
Half-close the write side; recv still drains.
-spec close_stream(stream()) -> ok.
Close a V1 stream (both sides). Renamed from close/1 in 3.11.0 because close/1 now refers to the V2 pool surface.
-spec connect([macula_client:seed()], macula_client:opts()) -> {ok, pool()} | {error, term()}.
Connect to the Macula relay mesh and return a pool handle.
Seeds is a list of relay endpoints (URL binaries/strings or #{host, port} maps). The pool spawns one peering link per seed and routes ops with replication, replay, and event dedup. Returns immediately; link handshakes complete asynchronously.
See macula_client for the canonical pool implementation and macula_pubsub for the slice module.
**Breaking change since 3.11.0**: this function previously returned a macula_mesh_client single-connection client. V1 callers must now call macula_mesh_client:start_link/1 directly to retain V1 semantics. See CHANGELOG.md / migration guide.
-spec disconnect(client()) -> ok.
Disconnect a V1 macula_mesh_client client. **Legacy** — V2 pools use close/1.
-spec ensure_distributed() -> ok | {error, term()}.
Ensure this node is running in distributed mode.
-spec find_record(client(), record_key()) -> {ok, record()} | {error, not_found | term()}.
Fetch a record by its macula_record:storage_key/1.
Returns {error, not_found} when no record exists at the key. The returned record's signature should be verified via macula_record:verify/1 before its payload is trusted.
-spec find_records_by_type(client(), record_type()) -> {ok, [record()]} | {error, term()}.
Return every record of a given type currently visible from the connected relay.
Coverage depends on the relay's view of the DHT — a single relay sees its local replicas plus whatever its peers have gossiped. Aggregating across the full mesh requires querying multiple relays and deduplicating by record key.
-spec get_cookie() -> atom().
Get the Erlang cluster cookie.
Enable Erlang distribution over a dedicated dist relay (macula-io/macula-dist-relay).
Different from join_mesh/1: - Connects to a dist relay (port 4434, ALPN macula-dist), NOT the pub/sub station mesh - No mesh_client, no pub/sub subscriptions — only dist traffic - Uses raw QUIC stream routing with no MessagePack overhead
Options: - url (required): <<"quic://relay.example.com:4434">>
After this returns ok, standard OTP distribution (rpc:call/4, gen_server:call/3 across nodes, pg groups, etc.) works across firewalls via the dist relay.
Join the Macula relay mesh with Erlang distribution.
After calling this, standard OTP distribution works across firewalls. Options: relays (required list), realm, identity, site, tls_verify.
List all nodes connected to the relay (default 5s timeout).
List all nodes connected to the relay with options.
-spec monitor_nodes() -> ok.
Subscribe to node up/down events.
-spec open_stream(procedure() | client(), procedure() | term(), term() | map()) -> {ok, stream()} | {error, term()}.
Open a client-stream or bidi call.
Two shapes: open_stream(Procedure, Args, Opts) — LOCAL dispatch open_stream(Client, Procedure, Args) — REMOTE, default mode bidi
-spec open_stream(procedure() | client(), procedure() | term(), term() | map(), stream_mode() | map()) -> {ok, stream()} | {error, term()}.
Open a stream with explicit mode.
Two shapes: open_stream(Procedure, Args, Opts, Mode) — LOCAL dispatch, explicit mode open_stream(Client, Procedure, Args, Opts) — REMOTE via mesh client
V1 publish — single-connection client, no realm. **Legacy**. V2 callers use publish/4 or publish/5.
Publish to (Realm, Topic) on Pool. Equivalent to publish/5 with empty opts.
**Breaking change since 3.11.0**: this signature previously was publish(Client, Topic, Data, Opts) (V1). V1 callers using the 4-arg form must call macula_mesh_client:publish/3 directly to retain V1 semantics.
Publish to (Realm, Topic) on Pool with options. See macula_pubsub:publish/5 for honored opts.
Store a signed record in the mesh DHT.
Build the record via the typed constructors in macula_record (node_record/3,4, content_announcement/3,4, tombstone/3,4, realm_directory/3,4, procedure_advertisement/3,4, etc.) then sign it with macula_record:sign/2. The relay validates the signature on receipt; an invalid signature returns {error, bad_signature}. Successful stores propagate to the K-nearest peers in the DHT under the record's macula_record:storage_key/1.
Receive the next chunk (blocks).
Resolve a mesh name to node identity information.
Returns the node's identity including name, site_id, city, endpoint, and connected_at timestamp. Works for names, site_ids, or node_ids.
Send a binary chunk on the stream.
Send a chunk with explicit encoding.
Set the Erlang cluster cookie.
Server-side: emit the terminal reply value.
-spec subscribe(client(), topic(), fun((term()) -> ok) | pid()) -> {ok, reference()} | {error, term()}.
V1 subscribe — single-connection client, no realm. **Legacy**. V2 callers use subscribe/4 or subscribe/5.
Subscribe Subscriber to (Realm, Topic) on Pool. Equivalent to subscribe/5 with empty opts.
Subscribe Subscriber to (Realm, Topic) on Pool with options. See macula_pubsub:subscribe/5.
-spec subscribe_records(client(), record_type(), fun((record()) -> any()) | pid()) -> {ok, reference()} | {error, term()}.
Subscribe to live record-stored events filtered by type.
The callback (or pid) receives each newly-stored record of the given type as {record, Record} (pid form) or via direct invocation (fun form). Returns a subscription reference for unsubscribe_records/2. Topic shape is _dht.records.<type>.stored, rendered with the type tag as a decimal integer for log friendliness.
Stop advertising a procedure.
-spec unadvertise_stream(procedure()) -> ok.
Stop advertising a streaming procedure.
-spec unmonitor_nodes() -> ok.
Unsubscribe from node up/down events.
Drop a V2 pool subscription. Idempotent.
**Breaking change since 3.11.0**: this routes to the V2 pool (macula_client:unsubscribe/2). V1 callers must call macula_mesh_client:unsubscribe/2 directly to drop a V1 sub.
Cancel a subscribe_records/3 subscription.