Core
The core is plain Lua 5.4 and loads into every resource that uses Rivo FX.
Fx.succeed / fail / die / sync / try / suspend / async |
build effects |
:map :flatMap :andThen :tap :zip |
chain |
:catchAll :catchTag :catchTags :mapError :orElse :result :option |
handle errors |
Fx.gen(function() local x = Fx.yield(eff) end) |
direct style (eff:await() also works inside a gen) |
:timeout(ms) :retry(policy) :repeatWith(schedule) :delay(ms) |
time |
Fx.Schedule.exponential(100):take(5):jittered() |
retry and repeat schedules |
:fork :forkDaemon :forkScoped, Fx.forEach(list, f, { concurrency = 4 }), Fx.all, :race |
concurrency |
Fx.Tag, Fx.serviceOption(tag), Fx.Layer.succeed/effect/scoped/merge, :provide(layer), Fx.Runtime.make(layer) |
services |
Fx.scoped, Fx.acquireRelease, Fx.addFinalizer, :ensuring, :onExit |
resources |
Fx.Ref, Fx.Deferred, Fx.Semaphore, Fx.Queue, Fx.PubSub |
shared state |
Fx.Stream |
values over time (see Streams) |
Fx.S.* |
Schema |
Fx.TaggedError('Name') |
typed errors |
Fx.runFork / runAwait / runSync / runCallback |
run |
Inside Fx.gen, Citizen.Wait and Citizen.Await still work. Raising a tagged error with error(NotFound {}) fails the effect with that error. Any other raised error is a defect.
How fibers run
A fiber is a coroutine on a runner inside your resource, not a Citizen thread, so a parked fiber costs nothing and waking one is a plain coroutine.resume. Timers share one thread per resource, which sleeps until the next one is due.
Code in a fiber that calls Wait or Citizen.Await (in Fx.gen or plain Fx.sync code) borrows a Citizen thread for that wait and behaves exactly as it would on its own thread. Prefer Fx.sleep over Wait inside fibers: it stays on the runner and can be interrupted.
A fiber woken by another fiber (a Queue offer, a Deferred) runs once the waker parks or yields, not in the middle of it.