初探 MoonBit 中的 JavaScript 交互
引言
在当今的软件世界中,任何一门编程语言都无法成为一座孤岛。 对于 MoonBit 这样一门新兴的通用编程语言而言,若想在庞大的技术生态中茁壮成长,与现有生态系统的无缝集成便显得至关重要。
MoonBit 提供了包括 JavaScript 在内的多种编译后端,这为其对接广阔的 JavaScript 生态敞开了大门。 无论是对于浏览器前端开发,还是对于 Node.js 环境下的后端应用,这种集成能力都极大地拓展了 MoonBit 的应用场景,让开发者可以在享受 MoonBit 带来的类型安全与高性能的同时,复用数以万计的现有 JavaScript 库。
在本文中,我们将以 Node.js 环境为例,一步步探索 MoonBit JavaScript FFI 的奥秘,从基础的函数调用到复杂的类型与错误处理,向你展示如何优雅地搭建连接 MoonBit 与 JavaScript 世界的桥梁。
预先准备
在正式启程之前,我们需要先为项目做好基础配置。如果还没有现成的项目,可以使用 moon new
工具创建一个新的 MoonBit 项目。
为了让 MoonBit 工具链知晓我们的目标平台是 JavaScript,我们需要在项目根目录的 moon.mod.json
文件中添加以下内容:
{
"preferred-target": "js"
}
此项配置会告知编译器,在执行 moon build
或 moon check
等命令时,默认使用 JavaScript 后端。
当然,如果你希望在命令行中临时指定,也可以通过 --target=js
参数达到同样的效果。
编译项目
完成上述配置后,只需在项目根目录下运行我们所熟悉的构建命令:
> moon build
命令执行成功后,由于我们的项目默认包含一个可执行入口,你可以在 target/js/debug/build/
目录下找到编译产物。MoonBit 非常贴心地为我们生成了三个文件:
.js
文件:编译后的 JavaScript 源码。.js.map
文件:用于调试的 Source Map 文件。.d.ts
文件:TypeScript 类型声明文件,便于在 TypeScript 项目中集成。
第一个 JavaScript API 调用
MoonBit 的 FFI 设计在原则上保持了一致性。与调用 C 或其他语言类似,我们通过一个带有 extern
关键字的函数声明来定义一个外部调用:
extern "js" fn consoleLog(msg : String
String) -> Unit
Unit = "(msg) => console.log(msg)"
这行代码是 FFI 的核心。让我们来分解一下:
-
extern "js"
:声明这是一个指向 JavaScript 环境的外部函数。 -
fn consoleLog(msg : String) -> Unit
:这是该函数在 MoonBit 中的类型签名,它接受一个String
类型的参数,并且返回一个单位值 (Unit
)。 -
"(msg) => console.log(msg)"
:等号右侧的字符串字面量是这段 FFI 的“灵魂”,其中需要包含一段原生 JavaScript 函数。在这里,我们使用了一个简洁的箭头函数。 MoonBit 编译器会按原样将这段代码嵌入到最终生成的
.js
文件中,从而实现从 MoonBit 到 JavaScript 的调用。提示 如果你的 JavaScript 代码片段比较复杂,可以使用
#|
语法来定义多行字符串,以提高可读性。
一旦这个 FFI 声明就绪,我们就可以在 MoonBit 代码中像调用普通函数一样调用 consoleLog
了:
test "hello" {
(msg : String) -> Unit
consoleLog("Hello from JavaScript!")
}
运行 moon test
,你将会在控制台看到由 JavaScript console.log
打印出的信息。我们的第一座桥梁已经成功搭建!
JavaScript 类型的对接
打通调用流程只是第一步,真正的挑战在于如何处理两种语言之间的类型差异。 MoonBit 是一门静态类型语言,而 JavaScript 则是动态类型语言。如何在这两者之间建立安全可靠的类型映射,是 FFI 设计中需要重点考虑的问题。
下面,我们从易到难,分情况介绍如何在 MoonBit 中对接不同的 JavaScript 类型。
无需转换的 JavaScript 类型
最简单的情况是,MoonBit 中的某些类型在编译到 JavaScript 后端时,其底层实现本身就是对应的原生 JavaScript 类型。在这种情况下,我们可以直接进行传递,无需任何转换。
常见的“零成本”对接类型如下表所示:
MoonBit 类型 | JavaScript 对应类型 |
---|---|
String | string |
Bool | boolean |
Int , UInt , Float , Double | number |
BigInt | bigint |
Bytes | Uint8Array |
Array[T] | Array<T> |
函数类型 | Function |
基于这些对应关系,我们已经能够对许多简单的 JavaScript 函数进行绑定了。
事实上,在之前绑定 console.log
函数的例子中,我们已经使用了 MoonBit 中 String
类型与 JavaScript 中 string
类型的对应关系。
注意:维持 MoonBit 类型的内部不变量
一个非常重要的细节是,MoonBit 的所有标准数值类型(
Int
,Float
等)在 JavaScript 中都对应于number
类型,即 IEEE 754 双精度浮点数。 这意味着当整数值越过 FFI 边界进入 JavaScript 后,其行为将遵循浮点数语义,这可能会导致在 MoonBit 看来非预期的结果,例如整数溢出行为的差异:extern "js" fn incr(x :
Int) ->Int
Int = "(x) => x + 1" test "incr" { // 在 MoonBit 中,@int.max_value + 1 会溢出并回绕Int
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
@int.max_valueInt
Maximum value of an integer.
+ 1,(self : Int, other : Int) -> Int
Adds two 32-bit signed integers. Performs two's complement arithmetic, which means the operation will wrap around if the result exceeds the range of a 32-bit integer.
Parameters:
self
: The first integer operand.other
: The second integer operand.Returns a new integer that is the sum of the two operands. If the mathematical sum exceeds the range of a 32-bit integer (-2,147,483,648 to 2,147,483,647), the result wraps around according to two's complement rules.
Example:
inspect(42 + 1, content="43") inspect(2147483647 + 1, content="-2147483648") // Overflow wraps around to minimum value
content="-2147483648") // 在 JavaScript 中,它被当作浮点数处理,不会溢出String
inspect((obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content. Used primarily in test cases to verify the correctness of
Show
implementations and program outputs.Parameters:
object
: The object to be inspected. Must implement theShow
trait.content
: The expected string representation of the object. Defaults to an empty string.location
: Source code location information for error reporting. Automatically provided by the compiler.arguments_location
: Location information for function arguments in source code. Automatically provided by the compiler.Throws an
InspectError
if the actual string representation of the object does not match the expected content. The error message includes detailed information about the mismatch, including source location and both expected and actual values.Example:
inspect(42, content="42") inspect("hello", content="hello") inspect([1, 2, 3], content="[1, 2, 3]")
incr((x : Int) -> Int
@int.max_value),Int
Maximum value of an integer.
content="2147483648") // ??? }String
而这本质上是不合法的,因为根据 MoonBit 中
Int
的值的内部不变量,其值不可能是2147483648
(超出了类型允许的最大值)。 这可能导致下游依赖这一点的其他 MoonBit 代码出现意料之外的行为。 在跨越 FFI 边界处理其他数据类型时也有可能出现类似的问题,因此请在编写相关逻辑时务必留意这一点。
外部 JavaScript 类型
当然,JavaScript 的世界远比上述基本类型要丰富。
我们很快就会遇到 undefined
、null
、symbol
以及各种复杂的宿主对象(Host Object)。这些类型在 MoonBit 中没有直接的对应物。
对于这种情况,MoonBit 提供了 #external
注解。
这个注解好比一个契约,它告诉编译器:
“请相信我,这个类型在外部世界(JavaScript)中是真实存在的。
你不需要关心它的内部结构,只需把它当作一个不透明的句柄来处理即可。”
例如,我们可以这样定义一个代表 JavaScript undefined
的类型:
#external
type Undefined
extern "js" fn Undefined::new() -> Self = "() => undefined"
然而,单独的 Undefined
类型意义不大,因为在实际应用中,undefined
往往是作为联合类型(Union Type)的一部分出现的,例如 string | undefined
。
一个更实用的方案是创建一个 Optional[T]
类型来精确对应 JavaScript 中的 T | undefined
,并让它能与 MoonBit 内置的 T?
(Option[T]
)类型方便地互相转换。
为了实现这个目标,我们首先需要一个能够代表“任意” JavaScript 值的类型,类似于 TypeScript 中的 any
。这正是 #external
的用武之地:
#external
pub type Value
相应地,我们还需要提供获取 undefined
值和判断某值是否为 undefined
的方法:
extern "js" fn type Value
Value::undefined() -> type Value
Value =
#| () => undefined
extern "js" fn type Value
Value::is_undefined(self : type Value
Self) -> Bool
Bool =
#| (n) => Object.is(n, undefined)
为了方便调试,我们再为 Value
类型实现 Show
特质,让它可以被打印出来:
pub impl trait Show {
output(Self, &Logger) -> Unit
to_string(Self) -> String
}
Trait for types that can be converted to String
Show for type Value
Value with (self : Value, logger : &Logger) -> Unit
output(Value
self, &Logger
logger) {
&Logger
logger.(&Logger, String) -> Unit
write_string(Value
self.(self : Value) -> String
to_string())
}
pub extern "js" fn type Value
Value::to_string(self : type Value
Value) -> String
String =
#| (self) =>
#| self === undefined ? 'undefined'
#| : self === null ? 'null'
#| : self.toString()
接下来是整个转换过程中的“魔法”所在。我们定义两个特殊的转换函数:
fn[T] type Value
Value::cast_from(value : type parameter T
T) -> type Value
Value = "%identity"
fn[T] type Value
Value::cast(self : type Value
Self) -> type parameter T
T = "%identity"
何为
%identity
%identity
是 MoonBit 提供的一个特殊内建函数(intrinsic),它是一个“零成 本”的类型转换操作。 它在编译时会进行类型检查,但在运行时不会产生任何效果。 它仅仅是告诉编译器:“作为开发者,我比你更清楚这个值的真实类型,请直接将它当作另一种类型来看待。”这是一把双刃剑:它为 FFI 边界层的代码提供了强大的表达能力,但如果滥用,则可能破坏类型安全。 因此,它的使用场景应当被严格限制在 FFI 相关代码范围内。
有了这些积木,我们就可以开始搭建 Optional[T]
了:
#external
type Optional[_] // 对应 T | undefined
/// 创建一个 undefined 的 Optional
fn[T] type Optional[_]
Optional::() -> Optional[T]
创建一个 undefined 的 Optional
undefined() -> type Optional[_]
Optional[type parameter T
T] {
type Value
Value::() -> Value
undefined().(self : Value) -> Optional[T]
cast()
}
/// 检查一个 Optional 是否为 undefined
fn[T] type Optional[_]
Optional::(self : Optional[T]) -> Bool
检查一个 Optional 是否为 undefined
is_undefined(Optional[T]
self : type Optional[_]
Optional[type parameter T
T]) -> Bool
Bool {
Optional[T]
self |> type Value
Value(Optional[T]) -> Value
::cast_from |> type Value
Value(Value) -> Bool
::is_undefined
}
/// 从 Optional[T] 中解包出 T,如果为 undefined 则 panic
fn[T] type Optional[_]
Optional::(self : Optional[T]) -> T
从 Optional[T] 中解包出 T,如果为 undefined 则 panic
unwrap(Optional[T]
self : type Optional[_]
Self[type parameter T
T]) -> type parameter T
T {
guard Bool
!Optional[T]
selfBool
.(self : Optional[T]) -> Bool
检查一个 Optional 是否为 undefined
is_undefinedBool
() else { (msg : String) -> T
Aborts the program with an error message. Always causes a panic, regardless
of the message provided.
Parameters:
message
: A string containing the error message to be displayed when
aborting.
Returns a value of type T
. However, this function never actually returns a
value as it always causes a panic.
abort("Cannot unwrap an undefined value") }
type Value
Value::(value : Optional[T]) -> Value
cast_from(Optional[T]
self).(self : Value) -> T
cast()
}
/// 将 Optional[T] 转换为 MoonBit 内置的 T?
fn[T] type Optional[_]
Optional::(self : Optional[T]) -> T?
将 Optional[T] 转换为 MoonBit 内置的 T?
to_option(Optional[T]
self : type Optional[_]
Optional[type parameter T
T]) -> type parameter T
T? {
guard Bool
!type Value
ValueBool
::(value : Optional[T]) -> Value
cast_fromBool
(Optional[T]
selfBool
).(self : Value) -> Bool
is_undefinedBool
() else { T?
None }
(T) -> T?
Some(type Value
Value::(value : Optional[T]) -> Value
cast_from(Optional[T]
self).(self : Value) -> T
cast())
}
/// 从 MoonBit 内置的 T? 创建 Optional[T]
fn[T] type Optional[_]
Optional::(value : T?) -> Optional[T]
从 MoonBit 内置的 T? 创建 Optional[T]
from_option(T?
value : type parameter T
T?) -> type Optional[_]
Optional[type parameter T
T] {
guard T?
value is (T) -> T?
Some(T
v) else { type Optional[_]
Optional::() -> Optional[T]
创建一个 undefined 的 Optional
undefined() }
type Value
Value::(value : T) -> Value
cast_from(T
v).(self : Value) -> Optional[T]
cast()
}
test "Optional from and to Option" {
let Optional[Int]
optional = type Optional[_]
Optional::(value : Int?) -> Optional[Int]
从 MoonBit 内置的 T? 创建 Optional[T]
from_option((Int) -> Int?
Some(3))
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Optional[Int]
optional.(self : Optional[Int]) -> Int
从 Optional[T] 中解包出 T,如果为 undefined 则 panic
unwrap(), String
content="3")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Optional[Int]
optional.(self : Optional[Int]) -> Bool
检查一个 Optional 是否为 undefined
is_undefined(), String
content="false")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Optional[Int]
optional.(self : Optional[Int]) -> Int?
将 Optional[T] 转换为 MoonBit 内置的 T?
to_option(), String
content="Some(3)")
let Optional[Int]
optional : type Optional[_]
Optional[Int
Int] = type Optional[_]
Optional::(value : Int?) -> Optional[Int]
从 MoonBit 内置的 T? 创建 Optional[T]
from_option(Int?
None)
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Optional[Int]
optional.(self : Optional[Int]) -> Bool
检查一个 Optional 是否为 undefined
is_undefined(), String
content="true")
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(Optional[Int]
optional.(self : Optional[Int]) -> Int?
将 Optional[T] 转换为 MoonBit 内置的 T?
to_option(), String
content="None")
}
通过这套组合拳,我们成功地在 MoonBit 的类型系统中为 T | undefined
找到了一个安全且人体工学良好的表达方式。
同样的方法也可以用于对接 null
、symbol
、RegExp
等其他 JavaScript 特有的类型。
处理 JavaScript 错误
一个健壮的 FFI 层必须能够优雅地处理错误。
默认情况下,如果在 FFI 调用中,JavaScript 代码抛出了一个异常,这个异常并不会被 MoonBit 的 try-catch
机制捕获,而是会直接中断整个程序的执行:
// 这是一个会抛出异常的 FFI 调用
extern "js" fn boom_naive() -> Value raise = "(u) => undefined.toString()"
test "boom_naive" {
// 这段代码会直接让测试进程崩溃,而不是通过 `try?` 返回一个 `Result`
inspect(try? boom_naive()) // failed: TypeError: Cannot read properties of undefined (reading 'toString')
}
正确的做法是在 JavaScript 层用 try...catch
语句将调用包裹起来,然后找到一种办法将成功的结果或捕获到的错误传递回 MoonBit。
当然,我们可以直接在 extern "js"
声明的 JavaScript 代码中这么做,但也存在更可复用的解决办法:
首先,我们定义一个 Error_
类型来封装来自 JavaScript 的错误:
suberror Error_ type Value
Value
pub impl trait Show {
output(Self, &Logger) -> Unit
to_string(Self) -> String
}
Trait for types that can be converted to String
Show for suberror Error_ Value
Error_ with (self : Error_, logger : &Logger) -> Unit
output(Error_
self, &Logger
logger) {
&Logger
logger.(&Logger, String) -> Unit
write_string("@js.Error: ")
let (Value) -> Error_
Error_(Value
inner) = Error_
self
&Logger
logger.(self : &Logger, obj : Value) -> Unit
write_object(Value
inner)
}
接着,我们定义一个核心的 FFI 包装函数 Error_::wrap_ffi
。
它的作用是在 JavaScript 领域执行一个操作(op
),并根据成功与否,调用不同的回调函数(on_ok
或 on_error
):
extern "js" fn suberror Error_ Value
Error_::wrap_ffi(
op : () -> type Value
Value,
on_ok : (type Value
Value) -> Unit
Unit,
on_error : (type Value
Value) -> Unit
Unit,
) -> Unit
Unit =
#| (op, on_ok, on_error) => { try { on_ok(op()); } catch (e) { on_error(e); } }
最后,我们利用这个 FFI 函数和 MoonBit 的闭包,就可以封装出一个符合 MoonBit 风格、返回 T raise Error_
的 Error_::wrap
函数:
fn[T] suberror Error_ Value
Error_::(op : () -> Value, map_ok? : (Value) -> T) -> T raise Error_
wrap(
() -> Value
op : () -> type Value
Value,
(Value) -> T
map_ok~ : (type Value
Value) -> type parameter T
T = type Value
Value(Value) -> T
::cast,
) -> type parameter T
T raise suberror Error_ Value
Error_ {
// 定义一个变量,用于在闭包内外传递结果
let mut Result[Value, Error_]
res : enum Result[A, B] {
Err(B)
Ok(A)
}
Result[type Value
Value, suberror Error_ Value
Error_] = (Value) -> Result[Value, Error_]
Ok(type Value
Value::() -> Value
undefined())
// 调用 FFI,传入两个闭包,它们会根据 JS 的执行结果修改 res 的值
suberror Error_ Value
Error_::(op : () -> Value, on_ok : (Value) -> Unit, on_error : (Value) -> Unit) -> Unit
wrap_ffi(() -> Value
op, fn(Value
v) { Result[Value, Error_]
res = (Value) -> Result[Value, Error_]
Ok(Value
v) }, fn(Value
e) { Result[Value, Error_]
res = (Error_) -> Result[Value, Error_]
Err((Value) -> Error_
Error_(Value
e)) })
// 检查 res 的值,并返回相应的结果或抛出错误
match Result[Value, Error_]
res {
(Value) -> Result[Value, Error_]
Ok(Value
v) => (Value) -> T
map_ok(Value
v)
(Error_) -> Result[Value, Error_]
Err(Error_
e) => raise Error_
e
}
}
现在,我们可以安全地调用之前那个会抛出异常的函数了,并且能以纯 MoonBit 代码来处理可能发生的错误:
extern "js" fn boom() -> type Value
Value = "(u) => undefined.toString()"
test "boom" {
let Result[Value, Error_]
result = try? suberror Error_ Value
Error_::(op : () -> Value, map_ok? : (Value) -> Value) -> Value raise Error_
wrap(() -> Value
boom)
(obj : &Show, content~ : String, loc~ : SourceLoc = _, args_loc~ : ArgsLoc = _) -> Unit raise InspectError
Tests if the string representation of an object matches the expected content.
Used primarily in test cases to verify the correctness of Show
implementations and program outputs.
Parameters:
object
: The object to be inspected. Must implement the Show
trait.
content
: The expected string representation of the object. Defaults to
an empty string.
location
: Source code location information for error reporting.
Automatically provided by the compiler.
arguments_location
: Location information for function arguments in
source code. Automatically provided by the compiler.
Throws an InspectError
if the actual string representation of the object
does not match the expected content. The error message includes detailed
information about the mismatch, including source location and both expected
and actual values.
Example:
inspect(42, content="42")
inspect("hello", content="hello")
inspect([1, 2, 3], content="[1, 2, 3]")
inspect(
(Result[Value, Error_]
result : enum Result[A, B] {
Err(B)
Ok(A)
}
Result[type Value
Value, suberror Error_ Value
Error_]),
String
content="Err(@js.Error: TypeError: Cannot read properties of undefined (reading 'toString'))",
)
}