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

local funcs = require("funcs")

call

Calls a registered function synchronously and waits for its result.

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.

-- 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.

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.

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.

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.

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.

-- 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.

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.

-- 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 for the Future object reference.

response and channel

Returns the channel used to receive the result.

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.

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.

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.

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.

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.

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.

-- 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 for working with errors.