Packages
macula
0.7.8
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
src/macula_client.erl.bak
%%%-------------------------------------------------------------------
%%% @doc
%%% Macula SDK - Main API module for HTTP/3 mesh client operations.
%%%
%%% This module provides the primary interface for applications to
%%% connect to Macula mesh networks and perform pub/sub and RPC
%%% operations over HTTP/3 (QUIC) transport.
%%%
%%% == Quick Start ==
%%%
%%% Connect to a mesh, publish events, subscribe to topics, and make RPC calls.
%%% See individual function documentation for detailed examples with code.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(macula_client).
%% API exports
-export([
connect/2,
disconnect/1,
publish/3,
publish/4,
subscribe/3,
unsubscribe/2,
call/3,
call/4,
advertise/3,
advertise/4,
unadvertise/2
]).
%% Type exports
-export_type([
client/0,
topic/0,
event_data/0,
procedure/0,
args/0,
options/0,
subscription_ref/0
]).
%%%===================================================================
%%% Types
%%%===================================================================
-type client() :: pid().
%% Reference to a connected Macula mesh client.
-type topic() :: binary().
%% Topic name for pub/sub operations. Topics should describe event types,
%% not entity IDs. Example: `"my.app.user.registered"' (good),
%% not `"my.app.user.123.registered"' (bad - ID belongs in payload).
-type event_data() :: map() | binary().
%% Event payload data. Typically a map that will be JSON-encoded.
-type procedure() :: binary().
%% RPC procedure name. Example: `"my.app.get_user"'.
-type args() :: map() | list() | binary().
%% Arguments for RPC calls.
-type options() :: map().
%% Connection or operation options.
-type subscription_ref() :: reference().
%% Reference to an active subscription for unsubscribe operations.
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Connect to a Macula mesh network.
%%
%% Creates a new HTTP/3 (QUIC) connection to the specified mesh endpoint.
%%
%% == Options ==
%%
%% <ul>
%% <li>`realm' - Required. Binary realm identifier (e.g., `<<"my.app.realm">>')</li>
%% <li>`auth' - Optional. Authentication map with `api_key' or other auth methods</li>
%% <li>`timeout' - Optional. Connection timeout in milliseconds (default: 5000)</li>
%% <li>`node_id' - Optional. 32-byte node ID (generated if not provided)</li>
%% </ul>
%%
%% == Examples ==
%%
%% ```
%% %% Basic connection
%% {ok, Client} = macula_client:connect(<<"https://mesh.local:443">>, #{
%% realm => <<"my.realm">>
%% }).
%%
%% %% With API key authentication
%% {ok, Client} = macula_client:connect(<<"https://mesh.local:443">>, #{
%% realm => <<"my.realm">>,
%% auth => #{api_key => <<"secret-key">>}
%% }).
%% '''
-spec connect(Url :: binary(), Opts :: options()) ->
{ok, client()} | {error, Reason :: term()}.
connect(Url, Opts) when is_binary(Url), is_map(Opts) ->
macula_connection:start_link(Url, Opts);
connect(Url, Opts) when is_list(Url), is_map(Opts) ->
connect(list_to_binary(Url), Opts).
%% @doc Disconnect from the Macula mesh.
%%
%% Cleanly closes the HTTP/3 connection and cleans up all subscriptions.
-spec disconnect(Client :: client()) -> ok | {error, Reason :: term()}.
disconnect(Client) when is_pid(Client) ->
macula_connection:stop(Client).
%% @doc Publish an event to a topic.
%%
%% Publishes data to the specified topic. All subscribers to this topic
%% will receive the event.
%%
%% == Topic Design ==
%%
%% Topics should describe EVENT TYPES, not entity instances:
%% <ul>
%% <li>Good: `<<"my.app.user.registered">>' (event type)</li>
%% <li>Bad: `<<"my.app.user.123.registered">>' (entity ID in topic)</li>
%% </ul>
%%
%% Entity IDs belong in the event payload, not the topic name.
%%
%% == Examples ==
%%
%% ```
%% %% Publish with default options
%% ok = macula_client:publish(Client, <<"my.app.events">>, #{
%% type => <<"user.registered">>,
%% user_id => <<"user-123">>,
%% email => <<"user@example.com">>
%% }).
%%
%% %% Publish with options
%% ok = macula_client:publish(Client, <<"my.app.events">>, #{
%% data => <<"important">>
%% }, #{acknowledge => true}).
%% '''
-spec publish(Client :: client(), Topic :: topic(), Data :: event_data()) ->
ok | {error, Reason :: term()}.
publish(Client, Topic, Data) when is_pid(Client), is_binary(Topic) ->
macula_connection:publish(Client, Topic, Data).
%% @doc Publish an event with options.
-spec publish(Client :: client(), Topic :: topic(), Data :: event_data(),
Opts :: options()) ->
ok | {error, Reason :: term()}.
publish(Client, Topic, Data, Opts) when is_pid(Client), is_binary(Topic), is_map(Opts) ->
macula_connection:publish(Client, Topic, Data, Opts).
%% @doc Subscribe to a topic.
%%
%% Subscribes to events on the specified topic. The callback function
%% will be invoked for each event received.
%%
%% == Callback Function ==
%%
%% The callback receives the event data and should return `ok'.
%%
%% == Examples ==
%%
%% ```
%% %% Simple subscription
%% {ok, SubRef} = macula_client:subscribe(Client, <<"my.app.events">>,
%% fun(EventData) ->
%% io:format("Event: ~p~n", [EventData]),
%% ok
%% end).
%%
%% %% Unsubscribe later
%% ok = macula_client:unsubscribe(Client, SubRef).
%% '''
-spec subscribe(Client :: client(), Topic :: topic(),
Callback :: fun((event_data()) -> ok)) ->
{ok, subscription_ref()} | {error, Reason :: term()}.
subscribe(Client, Topic, Callback) when is_pid(Client), is_binary(Topic), is_function(Callback, 1) ->
macula_connection:subscribe(Client, Topic, Callback).
%% @doc Unsubscribe from a topic.
%%
%% Removes the subscription identified by the subscription reference.
-spec unsubscribe(Client :: client(), SubRef :: subscription_ref()) ->
ok | {error, Reason :: term()}.
unsubscribe(Client, SubRef) when is_pid(Client), is_reference(SubRef) ->
macula_connection:unsubscribe(Client, SubRef).
%% @doc Make a synchronous RPC call.
%%
%% Calls a remote procedure and waits for the result.
%%
%% == Examples ==
%%
%% ```
%% %% Simple RPC call
%% {ok, User} = macula_client:call(Client, <<"my.app.get_user">>, #{
%% user_id => <<"user-123">>
%% }).
%%
%% %% With timeout
%% {ok, Result} = macula_client:call(Client, <<"my.app.process">>,
%% #{data => <<"large">>},
%% #{timeout => 30000}).
%% '''
-spec call(Client :: client(), Procedure :: procedure(), Args :: args()) ->
{ok, Result :: term()} | {error, Reason :: term()}.
call(Client, Procedure, Args) when is_pid(Client), is_binary(Procedure) ->
macula_connection:call(Client, Procedure, Args).
%% @doc Make an RPC call with options.
-spec call(Client :: client(), Procedure :: procedure(), Args :: args(),
Opts :: options()) ->
{ok, Result :: term()} | {error, Reason :: term()}.
call(Client, Procedure, Args, Opts) when is_pid(Client), is_binary(Procedure), is_map(Opts) ->
macula_connection:call(Client, Procedure, Args, Opts).
%% @doc Advertise a service that this client provides.
%%
%% Registers a handler function for the specified procedure and advertises
%% it to the DHT so other clients can discover and call it.
%%
%% The handler function receives a map of arguments and must return
%% `{ok, Result}' or `{error, Reason}'.
%%
%% == Options ==
%%
%% <ul>
%% <li>`ttl' - Advertisement TTL in seconds (default: 300)</li>
%% <li>`metadata' - Custom metadata map (default: #{})</li>
%% </ul>
%%
%% == Examples ==
%%
%% ```
%% %% Define a handler function
%% Handler = fun(#{user_id := UserId}) ->
%% {ok, #{user_id => UserId, name => <<"Alice">>}}
%% end.
%%
%% %% Advertise the service
%% {ok, Ref} = macula_client:advertise(
%% Client,
%% <<"my.app.get_user">>,
%% Handler
%% ).
%%
%% %% Other clients can now call:
%% %% {ok, User} = macula_client:call(OtherClient, <<"my.app.get_user">>,
%% %% #{user_id => <<"user-123">>}).
%% '''
-spec advertise(Client :: client(), Procedure :: procedure(),
Handler :: macula_service_registry:handler_fn()) ->
{ok, reference()} | {error, Reason :: term()}.
advertise(Client, Procedure, Handler) when is_pid(Client), is_binary(Procedure), is_function(Handler) ->
advertise(Client, Procedure, Handler, #{}).
%% @doc Advertise a service with options.
-spec advertise(Client :: client(), Procedure :: procedure(),
Handler :: macula_service_registry:handler_fn(),
Opts :: options()) ->
{ok, reference()} | {error, Reason :: term()}.
advertise(Client, Procedure, Handler, Opts) when is_pid(Client), is_binary(Procedure),
is_function(Handler), is_map(Opts) ->
macula_connection:advertise(Client, Procedure, Handler, Opts).
%% @doc Stop advertising a service.
%%
%% Removes the local handler and stops advertising to the DHT.
%%
%% == Examples ==
%%
%% ```
%% ok = macula_client:unadvertise(Client, <<"my.app.get_user">>).
%% '''
-spec unadvertise(Client :: client(), Procedure :: procedure()) ->
ok | {error, Reason :: term()}.
unadvertise(Client, Procedure) when is_pid(Client), is_binary(Procedure) ->
macula_connection:unadvertise(Client, Procedure).
%%%===================================================================
%%% Internal functions
%%%===================================================================