Packages
macula
0.8.19
7.1.0
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_discovery.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% Generic DHT-based service discovery.
%%% Provides cache-integrated lookup and announcement operations.
%%% Used by both pub/sub and RPC discovery layers.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_discovery).
%% API
-export([
find/2,
find_with_cache/3,
find_with_cache/4,
announce/2,
unannounce/2,
filter_by_age/3
]).
%% Types
-type key() :: binary().
-type value() :: term().
-type lookup_fun() :: fun((key()) -> {ok, [value()]} | {error, term()}).
-type publish_fun() :: fun((key(), value()) -> ok | {error, term()}).
-type unpublish_fun() :: fun((key()) -> ok | {error, term()}).
-export_type([
key/0,
value/0,
lookup_fun/0,
publish_fun/0,
unpublish_fun/0
]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Find values for a key via DHT lookup.
-spec find(key(), lookup_fun()) -> {ok, [value()]} | {error, term()}.
find(Key, LookupFun) ->
LookupFun(Key).
%% @doc Find values with cache (default TTL: 300 seconds).
-spec find_with_cache(key(), macula_cache:cache(), lookup_fun()) ->
{ok, [value()], macula_cache:cache()} | {error, term(), macula_cache:cache()}.
find_with_cache(Key, Cache, LookupFun) ->
find_with_cache(Key, Cache, LookupFun, 300).
%% @doc Find values with cache and custom TTL.
-spec find_with_cache(key(), macula_cache:cache(), lookup_fun(), pos_integer()) ->
{ok, [value()], macula_cache:cache()} | {error, term(), macula_cache:cache()}.
find_with_cache(Key, Cache, LookupFun, TTL) ->
%% Check cache first
case macula_cache:get(Cache, Key) of
{ok, Values, UpdatedCache} ->
%% Check if expired based on TTL
case is_cache_entry_expired(UpdatedCache, Key, TTL) of
true ->
%% Expired, query DHT
perform_dht_lookup(Key, Cache, LookupFun);
false ->
%% Valid cached entry
{ok, Values, UpdatedCache}
end;
not_found ->
%% Cache miss, query DHT
perform_dht_lookup(Key, Cache, LookupFun)
end.
%% @doc Announce value to DHT.
-spec announce(key(), publish_fun()) -> ok | {error, term()}.
announce(Key, PublishFun) ->
PublishFun(Key).
%% @doc Remove value from DHT.
-spec unannounce(key(), unpublish_fun()) -> ok | {error, term()}.
unannounce(Key, UnpublishFun) ->
UnpublishFun(Key).
%% @doc Filter items by age based on last_seen timestamp and TTL.
%% Items must have #{last_seen := integer()} in their structure.
-spec filter_by_age([map()], pos_integer(), atom()) -> [map()].
filter_by_age(Items, TTL, TimestampField) ->
Now = erlang:system_time(millisecond),
MaxAge = TTL * 1000, % TTL in seconds, convert to milliseconds
lists:filter(
fun(Item) ->
case maps:find(TimestampField, Item) of
{ok, LastSeen} ->
Age = Now - LastSeen,
Age =< MaxAge;
error ->
%% No timestamp field, assume expired
false
end
end,
Items
).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Perform DHT lookup and update cache.
-spec perform_dht_lookup(key(), macula_cache:cache(), lookup_fun()) ->
{ok, [value()], macula_cache:cache()} | {error, term(), macula_cache:cache()}.
perform_dht_lookup(Key, Cache, LookupFun) ->
case LookupFun(Key) of
{ok, Values} ->
%% Cache the result
UpdatedCache = macula_cache:put(Cache, Key, Values),
{ok, Values, UpdatedCache};
{error, Reason} ->
%% Return error, don't update cache
{error, Reason, Cache}
end.
%% @doc Check if cache entry is expired based on TTL.
-spec is_cache_entry_expired(macula_cache:cache(), key(), pos_integer()) -> boolean().
is_cache_entry_expired(#{entries := Entries}, Key, TTL) ->
case lists:search(fun(E) -> maps:get(key, E) =:= Key end, Entries) of
{value, Entry} ->
Timestamp = maps:get(timestamp, Entry),
Now = erlang:system_time(millisecond),
Age = Now - Timestamp,
Age > (TTL * 1000); % TTL in seconds, Age in milliseconds
false ->
true % Not found means expired
end.