///|
/// Invokes a typed command through the bridge installed on the active page.
pub async fn[Request : ToJson, Response : @json.FromJson] invoke(
command : @proton_contract.Command[Request, Response],
request : Request,
) -> Response raise ClientFailure {
command.validate() catch {
error => raise InvalidContract(message=error.message())
}
guard bridge_available() else { raise BridgeUnavailable }
let request_json = ToJson::to_json(request).stringify()
let response_json = @js_async.run_promise(signal => {
invoke_json(command.contract_route().operation_name(), request_json, signal)
}) catch {
error => raise decode_bridge_failure(error.to_string())
}
@json.from_json(@json.parse(response_json)) catch {
error => raise ResponseDecode(message=error.to_string())
}
}
///|
/// Invokes a typed command without entering the MoonBit async scheduler.
///
/// Framework adapters use this callback boundary when their effect runtime
/// owns scheduling. Application code should use `invoke`.
/// The returned function idempotently cancels the request if it is still active.
#doc(hidden)
pub fn[Request : ToJson, Response : @json.FromJson] invoke_with_callbacks(
command : @proton_contract.Command[Request, Response],
request : Request,
success : (Response) -> Unit,
failure : (ClientFailure) -> Unit,
) -> () -> Unit {
command.validate() catch {
error => {
failure(InvalidContract(message=error.message()))
return () => ()
}
}
let request_json = ToJson::to_json(request).stringify()
let controller = @js_async.AbortController::new()
invoke_json_with_callbacks(
command.contract_route().operation_name(),
request_json,
controller.signal(),
response_json => {
let response : Response = @json.from_json(@json.parse(response_json)) catch {
error => {
failure(ResponseDecode(message=error.to_string()))
return
}
}
success(response)
},
raw => failure(decode_bridge_failure(raw)),
)
() => controller.abort()
}
///|
/// A live event subscription installed on the active renderer page.
pub struct Subscription {
mut closed : Bool
close_listener : () -> Unit
}
///|
/// Removes this subscription. Repeated calls have no effect.
pub fn Subscription::close(self : Subscription) -> Unit {
if !self.closed {
self.closed = true
(self.close_listener)()
}
}
///|
/// Subscribes to a typed live event on the active renderer page.
///
/// A malformed payload reports `EventDecode` through `failure` without
/// removing the subscription.
pub fn[Payload : @json.FromJson] subscribe(
event : @proton_contract.Event[Payload],
received : (Payload) -> Unit,
failure : (ClientFailure) -> Unit,
) -> Subscription raise ClientFailure {
event.validate() catch {
error => raise InvalidContract(message=error.message())
}
guard bridge_available() else { raise BridgeUnavailable }
let close_listener = subscribe_json(event.contract_route().operation_name(), payload_json => {
try {
let payload : Payload = @json.from_json(@json.parse(payload_json))
received(payload)
} catch {
error => failure(EventDecode(message=error.to_string()))
}
})
Subscription::{ closed: false, close_listener }
}