///|
pub(all) enum EffectKind {
Immediately
AfterRender
}
///|
/// A deferred command handled by the Rabbita runtime.
///
/// `Cmd` models side effects in the update loop. Commands are returned from
/// `update` or embedded in `Html` event handlers, and are executed later by
/// the runtime.
enum Cmd {
Batch(Array[Cmd])
Invoke(Op, Extension)
}
///|
#doc(hidden)
#internal(experimental, "This API is unstable and may change in the future.")
pub fn flatten(key : @key.Key, cmd : Cmd) -> Array[(Op, Extension)] {
match cmd {
Batch(cmds) => cmds.map(x => flatten(key, x)).flatten()
Invoke(op, extension) => [(op, extension)]
}
}
///|
type OnExecute = (&Scheduler) -> Unit
///|
pub let immediately : EffectKind = Immediately
///|
pub let after_render : EffectKind = AfterRender
///|
/// A command that does nothing.
pub let none : Cmd = Batch([])
///|
/// Combine multiple commands into one command.
#inline
pub fn batch(cmds : Array[Cmd]) -> Cmd {
Batch(cmds)
}
///|
/// Create a **low-level** effect command for runtime and FFI integration.
/// **Consider use higher-level helpers like `perform`, `delay`, `@http.post()`, and
/// `@dialog.show()` in app code.**
///
/// This function allows you to write wrappers that encapsulate FFI in a
/// command when the functionality is not provided by Rabbita yet.
///
/// Parameters:
///
/// - `callback`: receives the runtime `Scheduler`, so it can enqueue follow-up
/// commands (for example, after an async callback).
///
/// - `kind`: determines when the effect runs.
/// - `Immediately`: run as soon as the runtime receives it.
/// - `AfterRender`: run after DOM patching for the current flush completes.
///
/// Example:
///
/// ```moonbit nocheck
/// extern "js" fn set_timeout(f : () -> Unit, ms : Int) = "(f,ms) => setTimeout(f, ms)"
///
/// pub fn delay(cmd : Cmd, ms : Int) -> Cmd {
/// raw_effect(scheduler => set_timeout(() => scheduler.add(cmd), ms))
/// }
/// ```
#inline
#alias(raw_effect, deprecated)
pub fn custom_cmd(
callback : (&Scheduler) -> Unit,
kind? : EffectKind = Immediately,
) -> Cmd {
op.request(LegacyEffect(kind, callback))
}
///|
/// Create a command that runs an effectful async function
#cfg(target="js")
pub fn effect(f : async () -> Unit noraise) -> Cmd {
custom_cmd(_ => @js.async_run(() => f()))
}
///|
/// Create a command that runs an async function.
///
/// The async function `f` is executed, then its result is converted into a new
/// command by `msg` and scheduled back into the update loop.
#cfg(target="js")
pub fn[A] perform(msg : (A) -> Cmd, f : async () -> A noraise) -> Cmd {
custom_cmd(scheduler => @js.async_run(() => scheduler.add(msg(f()))))
}
///|
/// Create a command that runs an async function and handles errors.
///
/// Similar to `perform`, but captures errors and passes `Result[A, E]` to `msg`.
#cfg(target="js")
pub fn[A, E : Error] attempt(
msg : (Result[A, E]) -> Cmd,
f : async () -> A raise E,
) -> Cmd {
custom_cmd(scheduler => {
@js.async_run(() => {
let msg = try f() catch {
e => msg(Err(e))
} noraise {
r => msg(Ok(r))
}
scheduler.add(msg)
})
})
}