Rivo FX
Rivo FX gives Lua and JS/TS resources the model Effect made popular: errors as values, fibers that clean up after themselves, services through Layers, and Schema at every trust boundary. On top of that it adds the FiveM pieces scripts get wrong most often:
- Net events that validate first. A handler never sees a payload that fails its schema, and every player is rate limited per event by default.
- RPC with timeouts and typed errors. A
NotEnoughMoneyraised on the server arrives on the client asNotEnoughMoney, socatchTagworks across the wire. Defects never leak details to clients. - Cleanup that actually runs. Scopes close when the resource stops or the player drops. Spawned entities get deleted, models released and in-flight handlers interrupted.
- No more
while not HasModelLoaded(m) do Wait(0) end. Asset loads have timeouts and typed errors.
It also comes with metrics and tracing and in-game devtools (/fxdev). Free and MIT licensed. Lua 5.4, FiveM. Source on GitHub.
Install
- Download
rivo_fx.zipfrom the latest release, unzip it into your resources folder (the folder must be namedrivo_fx) andensure rivo_fxbefore anything that uses it. - In your resource:
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
dependency 'rivo_fx'
shared_script '@rivo_fx/init.lua'
The library loads into your resource's own runtime, the way ox_lib does. No exports sit on the hot path, and FiveM modules load on first use.
Types: add rivo_fx/types to your LuaLS library:
{ "workspace.library": ["../rivo_fx/types"] }
JS / TS resources: npm install @rivolabs/fx effect (see JavaScript, TypeScript and NUI).
A taste
local S = Fx.S
local NotEnoughMoney = Fx.TaggedError('NotEnoughMoney')
-- a service, provided by a Layer
local Bank = Fx.Tag('Bank')
local BankLive = Fx.Layer.succeed(Bank, {
withdraw = function(src, amount)
return Fx.suspend(function()
local have = balances[src] or 0
if have < amount then return NotEnoughMoney { have = have, need = amount }:fail() end
balances[src] = have - amount
return Fx.succeed(balances[src])
end)
end,
})
Fx.setRuntime(Fx.Runtime.make(BankLive))
-- server: validated, rate limited, typed
Fx.rpc.register('bank:withdraw', { input = S.struct { amount = S.int:between(1, 1e6) } },
function(src, input)
return Fx.gen(function()
local bank = Fx.yield(Bank)
return Fx.yield(bank.withdraw(src, input.amount))
end)
end)
-- client
Fx.runFork(Fx.gen(function()
local balance = Fx.yield(
Fx.rpc.call('bank:withdraw', { amount = 500 }, { timeout = 5000 })
:catchTag('NotEnoughMoney', function(e)
return Fx.succeed(e.have)
end)
)
print('balance now', balance)
end))
Start a resource
npx rivo-fx create my_shop # server + client
npx rivo-fx create my_shop --template lua-nui # plus a page using @rivolabs/fx-nui
Moving an existing resource over? The migration guide has before/after code for net events, ox_lib/ESX/QBCore callbacks, framework money, oxmysql, asset loading, threads and NUI.
Level 0: use it without learning Effect
Already have a codebase? Start with Fx.runAwait. It runs an effect from any Citizen thread and returns the value, or raises the error so pcall works:
CreateThread(function()
local hash = Fx.runAwait(Fx.assets.model('adder', { timeout = 5000 }))
-- ... spawn something
end)
Read next
- Core: effects, typed errors, fibers, scopes, Layers, Schema
- FiveM modules: net events, RPC, assets, entities, state bags, oxmysql, frameworks, NUI
- Streams
- Metrics and tracing
- Devtools (/fxdev)
- JavaScript, TypeScript and NUI
- Moving a resource to Rivo FX
What's next
- OpenTelemetry (OTLP) export of spans and metrics
- A layer graph view in
/fxdev - An ox_core framework layer
- A TypeScript resource template for
rivo-fx create
Found a bug or want to help? See CONTRIBUTING. Security issues go through private reporting.