> ## Documentation Index
> Fetch the complete documentation index at: https://flux-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Invocations

> Client-to-server round trips with return values, error propagation and timeouts.

An invocation is Flux's replacement for `RemoteFunction`. The client calls `Invoke`,
the calling thread yields, the server's `OnInvoke` handler runs, and its return values
come back to the caller.

## Direction

<Warning>
  Invocations only run from client to server.

  The `Client` type has `Invoke` but no `OnInvoke`. The `Server` type has `OnInvoke`
  but no `Invoke`. There is no server-to-client invocation in Flux, because the client's
  dispatcher only handles responses and the server's only handles requests.
</Warning>

If you need data that only a client has, model it as two events instead of a round
trip:

```luau theme={null}
-- Server: ask, then wait for the answer on a separate channel.
local Request = Flux.Server("Settings.Request")
local Reply = Flux.Server("Settings.Reply")

Reply:Connect(function(player, settings)
	applySettings(player, settings)
end)

Request:Fire(player)
```

```luau theme={null}
-- Client: answer when asked.
local Request = Flux.Client("Settings.Request")
local Reply = Flux.Client("Settings.Reply")

Request:Connect(function()
	Reply:Fire(getLocalSettings())
end)
```

<Tip>
  This is usually the better design anyway. A server thread yielding on a client's
  response is a thread an exploiter can park indefinitely, whereas two events let you
  choose your own timeout and default.
</Tip>

## Handling invocations on the server

Assign a function to `OnInvoke`. It receives the sending player first, then the
client's arguments, and may return any number of values.

```luau theme={null}
local Inventory = Flux.Server("Inventory")

Inventory.OnInvoke = function(player, action: string, itemId: string)
	if action == "equip" then
		return equip(player, itemId)
	elseif action == "count" then
		return countOf(player, itemId), maxStack(itemId)
	end

	error("Unknown action: " .. action)
end
```

<Info>
  `OnInvoke` is a property with a setter, not a method. Flux intercepts the assignment
  through `__newindex` and stores the handler in a table keyed by the event name.
  Assigning it twice replaces the previous handler, so there is only ever one handler
  per name, and it is shared by every `Server` object built with that name.
</Info>

<Note>
  If no `OnInvoke` handler is registered for a name, incoming requests are silently
  dropped. The client will not get an error; it will yield until the 60-second timeout
  expires. Register handlers during startup, before clients can reach them.
</Note>

## Invoking from the client

```luau theme={null}
local Inventory = Flux.Client("Inventory")

local equipped = Inventory:Invoke("equip", "sword")
local count, max = Inventory:Invoke("count", "potion")
```

`Invoke` yields the calling thread, so it has to be called from a thread that can
yield.

### Error propagation

The server handler runs under `pcall`. If it errors, the failure is sent back and
`Invoke` re-raises it on the client.

```luau theme={null}
local ok, err = pcall(function()
	return Inventory:Invoke("equip", "does_not_exist")
end)

if not ok then
	warn("Equip failed:", err)
end
```

<Warning>
  The error is transmitted as a value and re-raised with `error(tostring(...))`, so the
  client sees the message text only. Stack traces, error objects and custom metatables
  do not survive the trip. Return a structured result if the caller needs to branch on
  the failure:

  ```luau theme={null}
  -- Prefer this over error() when failure is expected.
  Inventory.OnInvoke = function(player, itemId)
      local item = findItem(player, itemId)
      if not item then
          return false, "not_found"
      end
      return true, item.Name
  end
  ```
</Warning>

### Timeouts

Every request starts a 60-second timer. If no response arrives before it expires, the
pending request is discarded and `Invoke` errors with `Request timed out`.

```luau theme={null}
local ok, result = pcall(function()
	return Inventory:Invoke("count", "potion")
end)

if not ok and result == "Request timed out" then
	warn("Server did not respond in 60s")
end
```

<Info>
  The timeout is a fixed internal constant, `REQUEST_TIMEOUT = 60`, and is not
  configurable through the public API. If you need a shorter deadline, race the invoke
  against your own `task.delay` rather than editing the module.
</Info>

### Concurrency

Requests are tracked by an incrementing per-name counter, so many invocations can be in
flight at once and each resumes its own thread with its own response.

```luau theme={null}
-- All three run concurrently; each returns to its own thread.
for _, itemId in { "sword", "shield", "potion" } do
	task.spawn(function()
		local count = Inventory:Invoke("count", itemId)
		print(itemId, count)
	end)
end
```

<Note>
  The server spawns each handler with `task.spawn`, so a slow handler never blocks other
  requests. It also means responses can come back out of order. Never assume the first
  invoke you sent is the first to return.
</Note>

## Invocations ignore schemas

<Warning>
  Schemas apply to events only. A schema encodes the payloads of `Fire`,
  `FireUnreliable`, `FireAll` and `FireAllUnreliable`. `Invoke` requests and their
  responses always fall back to the dynamic `WriteAny`/`ReadAny` encoder, whatever
  schema you passed to `Flux.Server` or `Flux.Client`.
</Warning>

In practice this means invoke arguments and return values always carry one type tag per
value. That is slightly larger on the wire, and it is also why `OnInvoke` can return a
variable number of values without you declaring anything.

## Reliability

Requests and responses are always sent over the reliable channel. There is no
unreliable invocation, because a dropped request would simply hang until the timeout.

## Next steps

<CardGroup cols={2}>
  <Card title="Schemas" icon="ruler" href="/guides/schemas">
    Shrink your event payloads with fixed layouts.
  </Card>

  <Card title="Client API" icon="code" href="/api/client#invoke">
    The full `Invoke` signature and semantics.
  </Card>
</CardGroup>
