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

# Client

> The client-side event object returned by Flux.Client.

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

Created only by [`Flux.Client`](/api/flux#flux-client). Client-only, so constructing one on
the server errors.

## Type definition

```luau theme={null}
export type Client<Args... = (...any), Out... = (...any)> = {
	Fire: (self, Args...) -> (),
	FireUnreliable: (self, Args...) -> (),
	Invoke: (self, Args...) -> Out...,
	Connect: (self, callback: (Args...) -> ()) -> { Disconnect: () -> () },
	Once: (self, callback: (Args...) -> ()) -> (),
	Wait: (self) -> Args...,
}
```

<Note>
  Client callbacks receive only the event's arguments, with no `Player` parameter, since the
  server is the only possible sender. This is the most common source of off-by-one argument
  bugs when moving code between contexts.
</Note>

***

## Fire()

```luau theme={null}
function Client:Fire(...: any): ()
```

Sends to the server over the reliable channel. There is no target argument, because the
server is the only recipient.

```luau theme={null}
local Chat = Flux.Client("Chat")
Chat:Fire("Hello from the client!")
```

<Info>
  Returns immediately. The payload is appended to the client's reliable buffer and flushed on
  the next `Heartbeat`.
</Info>

***

## FireUnreliable()

```luau theme={null}
function Client:FireUnreliable(...: any): ()
```

Sends to the server over the unreliable channel. May be dropped or reordered.

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

RunService.RenderStepped:Connect(function()
	Aim:FireUnreliable(mouse.Hit.Position)
end)
```

<Warning>
  Never send anything the server must not miss over this channel, such as inputs that grant
  rewards, purchases, or state transitions. Use `Fire` for those.
</Warning>

***

## Invoke()

```luau theme={null}
function Client:Invoke(...: any): Out...
```

Sends a request to the server, yields the calling thread, and returns the values from the
server's [`OnInvoke`](/api/server#oninvoke) handler.

<ParamField path="..." type="Args...">
  Arguments forwarded to the server handler.
</ParamField>

<ResponseField name="returns" type="Out...">
  Whatever the server handler returned.
</ResponseField>

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

local ok, item = Shop:Invoke("sword")
print(ok, item) --> true sword
```

### Errors

`Invoke` raises rather than returning a failure flag, so wrap it when failure is expected:

```luau theme={null}
local success, result = pcall(function()
	return Shop:Invoke("legendary_sword")
end)

if not success then
	warn("Purchase failed:", result)
end
```

<ResponseField name="Handler errored" type="error(message)">
  The server handler's error message is transmitted and re-raised with
  `error(tostring(...))`. Only the message text survives: no stack trace, no error object, no
  metatable.
</ResponseField>

<ResponseField name="Timed out" type="error(&#x22;Request timed out&#x22;)">
  Raised if no response arrives within 60 seconds. The pending request is discarded at that
  point, so a late response is ignored.
</ResponseField>

<Info>
  `REQUEST_TIMEOUT = 60` is an internal constant and is not configurable through the public
  API. For 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-channel counter, so many can be in flight at
once and each resumes its own thread.

```luau theme={null}
for _, itemId in { "sword", "shield", "potion" } do
	task.spawn(function()
		print(itemId, Shop:Invoke("price", itemId))
	end)
end
```

<Warning>
  Responses can arrive out of order, because the server spawns each handler in its own
  thread. Never assume the first request you sent is the first to return.
</Warning>

<Note>
  Requests always travel over the reliable channel, and they never use the event's schema.
  See [Invocations](/guides/invocations#invocations-ignore-schemas).
</Note>

<Warning>
  There is no `Client.OnInvoke`. The server cannot invoke a client.
</Warning>

***

## Connect()

```luau theme={null}
function Client:Connect(callback: (...any) -> ()): { Disconnect: () -> () }
```

Registers a persistent listener for server sends.

<ParamField path="callback" type="(Args...) -> ()" required>
  Invoked once per received message, with the event's arguments only.
</ParamField>

<ResponseField name="returns" type="{ Disconnect: () -> () }">
  A plain table with a single `Disconnect` method, not an `RBXScriptConnection`.
</ResponseField>

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

local connection = Notify:Connect(function(message, duration)
	showToast(message, duration)
end)

connection:Disconnect()
```

<Warning>
  Listeners are never removed automatically. A `LocalScript` under
  `StarterCharacterScripts` re-runs on every respawn, so without a disconnect you accumulate
  one listener per life and your callback fires once per life, cumulatively. See
  [Connections](/guides/connections).
</Warning>

<Info>
  Callbacks are dispatched with `task.spawn` and the listener list is walked backwards, so
  listeners run concurrently and the most recently connected runs first.
</Info>

***

## Once()

```luau theme={null}
function Client:Once(callback: (...any) -> ()): ()
```

Fires at most once, then removes itself.

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

MapLoaded:Once(function(mapName)
	print("Playing on", mapName)
end)
```

<Note>
  Returns nothing, so there is no handle and it cannot be cancelled before it fires.
</Note>

***

## Wait()

```luau theme={null}
function Client:Wait(): ...any
```

Yields until the next message on this channel and returns its arguments.

<ResponseField name="returns" type="Args...">
  The payload, with no `Player`, unlike [`Server:Wait`](/api/server#wait).
</ResponseField>

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

local mapName = MapLoaded:Wait()
print("Playing on", mapName)
```

<Warning>
  Has to be called from a yieldable thread, and has no timeout, so a message that never
  arrives parks the thread for the session.
</Warning>

***

## Shared state

As on the server, the object is a thin typed handle over module-level tables keyed by
channel name.

<ResponseField name="self._id" type="string">
  The channel name. Private.
</ResponseField>

<ResponseField name="self._schema" type="{ Types.Type }?">
  The schema passed at construction. Private.
</ResponseField>

Construction also initialises this channel's pending-request table and request-id counter,
which is why `Invoke` works without any further setup.

```luau theme={null}
local a = Flux.Client("Ping")
local b = Flux.Client("Ping")

a:Connect(print)
-- A server send on "Ping" runs the listener registered through `a`,
-- regardless of which object you hold.
```

## Server and client differences

|                    | `Server`                       | `Client`        |
| ------------------ | ------------------------------ | --------------- |
| `Fire` signature   | `(player, ...)`                | `(...)`         |
| Broadcast methods  | `FireAll`, `FireAllUnreliable` | not available   |
| Round trips        | `OnInvoke` handler             | `Invoke` caller |
| `Connect` callback | `(player, ...)`                | `(...)`         |
| `Wait` returns     | `(Player, ...)`                | `(...)`         |

## Next steps

<CardGroup cols={2}>
  <Card title="Server" icon="server" href="/api/server">
    The other half of the pair.
  </Card>

  <Card title="Invocations" icon="arrow-right-arrow-left" href="/guides/invocations">
    Round trips end to end.
  </Card>
</CardGroup>
