# "Function Invocation"
_Path: en/lua/core/funcs_
> "The primary way to call other functions in Wippy. Execute registered functions synchronously or asynchronously across processes, with full support…"
## Table of Contents
- Function Invocation
## Content
# Function Invocation
The `funcs` module calls registered functions synchronously or asynchronously. An executor can propagate request context, security identity, and implementation-specific call options. This page is an API reference; target IDs, arguments, and application data represent surrounding code.
## Loading
```lua
local funcs = require("funcs")
```
## `call`
Calls a registered function synchronously and waits for its result.
```lua
local result, err = funcs.call("app.api:get_user", user_id)
if err then
return nil, err
end
print(result.name)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `target` | string | Function ID in format "namespace:name" |
| `...args` | any | Arguments passed to the function |
**Returns:** `result, error`
The target uses the `namespace:name` format.
## `async`
Starts a function call and returns a `Future` immediately. Futures allow other work to continue while the call runs and support multiple concurrent calls.
```lua
-- Start heavy computation without blocking
local future, err = funcs.async("app.process:analyze_data", large_dataset)
if err then
return nil, err
end
-- Do other work while computation runs...
-- Wait for result when ready
local ch = future:response()
local _, open = ch:receive()
if not open then
return nil, errors.new("future response channel closed")
end
local payload, result_err = future:result()
if result_err then
return nil, result_err
end
local result, data_err = payload:data()
if data_err then return nil, data_err end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `target` | string | Function ID in format "namespace:name" |
| `...args` | any | Arguments passed to the function |
**Returns:** `Future, error`
## `new`
Creates an `Executor` for calls that need custom context, security identity, or call options.
```lua
local exec = funcs.new()
```
**Returns:** `Executor`
## Executor
An executor stores call context and options. Its configuration methods return new executor instances, allowing a base configuration to be reused.
### `with_context`
Adds request-scoped values that will be available to the called function, such as trace IDs, session data, or feature flags.
```lua
local ctx = require("ctx")
-- Propagate request context to downstream services
local request_id, ctx_err = ctx.get("request_id")
if ctx_err then return nil, ctx_err end
local exec, err = funcs.new():with_context({
request_id = request_id,
feature_flags = {dark_mode = true}
})
if err then return nil, err end
local user, err = exec:call("app.api:get_user", user_id)
if err then return nil, err end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `values` | table | Key-value pairs to add to context |
**Returns:** `Executor, error`
### `with_actor`
Sets the security actor used for authorization checks in the called function.
```lua
local security = require("security")
local actor = security.actor() -- Get current user's actor
-- Call admin function with user's credentials
local exec, err = funcs.new():with_actor(actor)
if err then return nil, err end
local result, err = exec:call("app.admin:delete_record", record_id)
if err and err:kind() == errors.PERMISSION_DENIED then
return nil, errors.new({kind = errors.PERMISSION_DENIED, message = "User cannot delete records"})
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `actor` | Actor | Security actor (from security module) |
**Returns:** `Executor, error`
### `with_scope`
Sets the security scope for called functions. The scope defines the permissions available to the call.
```lua
local security = require("security")
local scope = security.new_scope()
local exec, err = funcs.new():with_scope(scope)
if err then return nil, err end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `scope` | Scope | Security scope (from security module) |
**Returns:** `Executor, error`
### `with_options`
Sets call options such as the retry policy or the overlay network. Options are merged over any preset options of the target function entry.
```lua
-- Retry transient failures up to 5 times with exponential backoff
local exec = funcs.new():with_options({
retry = { max_attempts = 5, initial_delay = 100 }
})
local result, err = exec:call("app.external:fetch_data", query)
if err then
-- All attempts failed, or the error was not retryable
end
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `options` | table | Call options |
| Option | Type | Description |
|--------|------|-------------|
| `retry.max_attempts` | int | Maximum attempts including the first (1 disables retry) |
| `retry.initial_delay` | int/duration | Delay before first retry (ms or duration string), default `100` |
| `retry.max_delay` | int/duration | Upper bound for the backoff delay (ms or duration string), default `10s` |
| `retry.backoff_factor` | number | Multiplier applied to the delay after each attempt, default `2.0` |
| `retry.jitter` | number | Random jitter fraction applied to each delay, default `0.1` |
| `retry.retry_kinds` | string[] | Only retry errors of these kinds; by default every kind except `Invalid`, `PermissionDenied` and `Internal` is retried |
| `retry.skip_kinds` | string[] | Never retry errors of these kinds |
| `network` | string | Registry ID of an overlay network to route the call's outbound traffic through; requires the `network.select` permission |
Only retryable errors trigger retries; non-retryable errors surface immediately. Temporal activity options are described in [Activities](temporal/activities.md).
The runtime-defined option is:
| Recognized option | Type | Description |
|-------------------|------|-------------|
| `network` | string | Registry ID of the outbound `network.*` entry |
**Returns:** `Executor, error`
Selecting a network requires `network.select` permission on that network ID.
### `call` and `async`
The executor versions of `call` and `async` use its configured context and options.
```lua
-- Build reusable executor with context
local exec = funcs.new()
:with_context({trace_id = "abc-123"})
:with_options({retry = {max_attempts = 3}})
-- Make multiple calls with same context
local users, users_err = exec:call("app.api:list_users")
if users_err then return nil, users_err end
local posts, posts_err = exec:call("app.api:list_posts")
if posts_err then return nil, posts_err end
```
## Future Invocation Summary
`async()` returns a future representing an in-progress invocation. The methods below cover the caller-facing steps for receiving, inspecting, or canceling that invocation. See [Future](./future.md) for the Future object reference.
### `response` and `channel`
Returns the channel used to receive the result.
```lua
local time = require("time")
local future, err = funcs.async("app.api:slow_operation", data)
if err then
return nil, err
end
local ch = future:response() -- or future:channel()
local timeout, err = time.after("5s")
if err then
return nil, err
end
local result = channel.select {
ch:case_receive(),
timeout:case_receive()
}
```
**Returns:** `Channel`
The response channel signals completion. After it becomes ready, call `future:result()` to obtain the cached value or the called function's error.
### `is_complete`
Checks whether the future has completed without blocking.
```lua
while not future:is_complete() do
-- do other work
local _, sleep_err = time.sleep("100ms")
if sleep_err then return nil, sleep_err end
end
local result, err = future:result()
```
**Returns:** `boolean`
### `is_canceled`
Returns `true` if the future has been marked canceled by its provider. See the cancellation limitation below.
```lua
if future:is_canceled() then
print("Operation was canceled")
end
```
**Returns:** `boolean`
### `result`
Returns the cached result when complete or `nil` while the operation is pending.
```lua
local value, err = future:result()
if err then
print("Failed:", err:message())
elseif value then
local data, data_err = value:data()
if data_err then return nil, data_err end
print("Got:", data)
end
```
**Returns:** `Payload|table|nil, error|nil`
### `error`
Returns the operation error when the future has failed.
```lua
local err, has_error = future:error()
if has_error then
print("Error kind:", err:kind())
end
```
**Returns:** `error|nil, boolean`
This method returns a non-retryable `INTERNAL` wrapper for a failed operation. Use `result()` to preserve the called function's original error metadata.
### `cancel`
Requests cancellation of the asynchronous operation.
```lua
local canceled, err = future:cancel()
if err then return nil, err end
```
**Returns:** `boolean, error`
In runtime v0.3.32a, function and contract futures share one process-global cancellation callback. When both providers are loaded, cancel() and is_canceled() are not a stable cross-provider contract. Do not use cancellation for application correctness; time out locally and ignore a late result until the runtime separates provider cancellation.
## Parallel Operations
Combine `async` with `channel.select` to run and collect multiple calls concurrently.
```lua
-- Start multiple operations in parallel
local f1, err = funcs.async("app.api:get_user", user_id)
if err then return nil, err end
local f2, err = funcs.async("app.api:get_orders", user_id)
if err then return nil, err end
local f3, err = funcs.async("app.api:get_preferences", user_id)
if err then return nil, err end
-- Wait for all to complete using channels
local user_ch = f1:channel()
local orders_ch = f2:channel()
local prefs_ch = f3:channel()
local pending = {
[user_ch] = {name = "user", future = f1},
[orders_ch] = {name = "orders", future = f2},
[prefs_ch] = {name = "preferences", future = f3}
}
local results = {}
while next(pending) do
local cases = {}
for ch in pairs(pending) do
cases[#cases + 1] = ch:case_receive()
end
local r = channel.select(cases)
local completed = pending[r.channel]
pending[r.channel] = nil
local payload, result_err = completed.future:result()
if result_err then
return nil, result_err
end
local data, data_err = payload:data()
if data_err then
return nil, data_err
end
results[completed.name] = data
end
```
## Permissions
Function operations are subject to security policy evaluation.
| Action | Resource | Description |
|--------|----------|-------------|
| `funcs.call` | Function ID | Call a specific function |
| `funcs.context` | `context` | Use `with_context()` to set custom context |
| `funcs.security` | `security` | Use `with_actor()` or `with_scope()` |
| `network.select` | Network ID | Use `with_options({network = ...})` to select an overlay network |
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Target empty | `errors.INVALID` | no |
| Namespace missing | `errors.INVALID` | no |
| Name missing | `errors.INVALID` | no |
| Permission denied | `errors.PERMISSION_DENIED` | no |
| Async outside a process | `errors.INTERNAL` | no |
| Subscribe failed | `errors.INTERNAL` | no |
| Async start dispatch failed | `errors.INTERNAL` | no |
| Function error | varies | varies |
See [Error Handling](lua/core/errors.md) for working with errors.
## Navigation
Previous: "Process Groups" (lua/core/pg)
Next: "Futures" (lua/core/future)