AST macros for NebulaAPI.
Provides:
defapi- Define an API method with node targetingon_nebula_nodes- Conditional compilation based on nodecall_on_node- Unicast call with node selectorcall_on_nodes- Multicast call with node selectorcall_on_all_nodes- Multicast call on all nodes
Summary
Functions
Multicast call on all available nodes.
Unicast call - execute API calls on a specific node.
Multicast call - execute API calls on multiple nodes.
Defines an API method with node targeting.
Conditional compilation based on node.
Functions
Multicast call on all available nodes.
Named alias of the selector-less call_on_nodes form: it targets every node
serving the method (i.e. with a registered worker for it).
Examples
call_on_all_nodes do
MyModule.api_method()
end
call_on_all_nodes timeout: 5000, strategy: :first do
MyModule.api_method()
end
Unicast call - execute API calls on a specific node.
The selector must be written literally at the call site — one of:
- A Nebula AST expression (like
@api,&db) - A literal function that receives the nodes_info map and returns a single node atom
- Omitted (the options-only form, or a literal
nil) — "no restriction": the call routes to the first available worker, with the block's options still applying.
A variable or computed selector is a compile error (branch in Elixir and write a separate
call_on_* per case). Distinct from omitting it, a selector function that returns nil
means "nothing matched" — the call fails with {:nebula_error, {:no_worker_on_node, nil}},
it never widens the target.
Inside the block, the innermost explicit routing wins: a call carrying its
own truthy node_selector:/multicast: trailing opts routes itself (the
block's routing and options are ignored for that call), and a routing key
explicitly set to nil opts the call out of the block, back to default
routing.
Examples
# With Nebula expression
call_on_node @api do
MyModule.api_method()
end
# With selector function
call_on_node fn nodes_info ->
nodes_info
|> Enum.filter(fn {_node, info} -> :worker in info.tags end)
|> Enum.map(fn {node, _} -> node end)
|> Enum.random()
end, timeout: 5000 do
MyModule.api_method()
end
# Options only — no selector: any available worker, with these options.
# The semantic with_options, free of the trailing-opts positional gotcha.
call_on_node timeout: 30_000 do
MyModule.api_method()
end
Multicast call - execute API calls on multiple nodes.
The selector must be written literally at the call site — one of:
- A Nebula AST expression (like
@api,&db) - all matching nodes - A literal function that receives the nodes_info map and returns a list of node atoms
- Omitted (the options-only form, or a literal
nil) — "no restriction": the call fans out to every node serving the method (likecall_on_all_nodes), with the block's options still applying.
A variable or computed selector is a compile error (branch in Elixir and write a separate
call_on_* per case). Distinct from omitting it, a selector function that returns nil
or [] means "nothing matched" — zero calls are made (:all returns [], :first
returns {:nebula_error, :no_success, []}, :quorum fails :quorum_unreachable); it never
widens the target.
Inside the block, the innermost explicit routing wins: a call carrying its
own truthy node_selector:/multicast: trailing opts routes itself (the
block's routing and options are ignored for that call), and a routing key
explicitly set to nil or false opts the call out of the block, back to
default routing — MyMod.f(x, multicast: false) inside a multicast block
is a plain default call.
Examples
# With Nebula expression (calls all &db nodes)
call_on_nodes &db do
MyModule.api_method()
end
# With selector function
call_on_nodes fn nodes_info ->
nodes_info
|> Enum.filter(fn {_node, info} -> :storage in info.tags end)
|> Enum.map(fn {node, _} -> node end)
end, timeout: 5000, strategy: :all do
MyModule.api_method()
end
# Options only — no selector: every node serving the method.
# `call_on_all_nodes` is the named alias of this form.
call_on_nodes strategy: :quorum, at_least: 2 do
MyModule.api_method()
end
Defines an API method with node targeting.
The method is compiled either as a local implementation or a remote stub depending on whether the current node matches the target nodes.
Node Selectors
Selectors are juxtaposed by a space (never commas, never a [list]):
@node- specific node (short or full name)&tag- nodes with the tag!@node/!&tag- negation@a &b- combine by juxtaposition (e.g.@worker &gpu)- (no selector) - the body runs on every node (local everywhere)
Examples
defapi @api, get_user(id) do
Repo.get(User, id)
end
defapi &db !@worker, find_podcast(slug) do
MyApp.Repo.find_one(:db, "records", %{identifier: slug})
end
# No selector → available on every node, each returns its own data.
defapi get_node_health() do
collect_runtime_info()
end
Conditional compilation based on node.
Just like if but for NebulaAPI. Only compiles the do block on matching nodes,
otherwise compiles the else block (if provided).
Examples
on_nebula_nodes &db do
# This code only exists on &db nodes
use MyApp.Repo, otp_app: :my_app
end
on_nebula_nodes @api do
# Code for @api nodes
else
# Code for other nodes
end