README

#@core - MoonBit/JavaScript Interop Core

@core is a minimal utility package for binding MoonBit objects to JavaScript objects. This package contains only the essential types and functions for JS interop, without JavaScript built-in APIs.

#Package Responsibility

Core responsibility: Provide minimal, zero-cost utilities for MoonBit ↔ JavaScript object binding.

This package includes:
  • ✅ Type casting functions (identity, any, cast)
  • ✅ Core binding types (Any, Nullable, Nullish, Promise)
  • ✅ Basic type checking (typeof_, instanceof_)
  • ✅ Property access and method calls (_get, _set, _call)
  • ✅ Object and array creation (new_object, new_array)
  • ✅ Function conversion (from_fn0, from_fn1, etc.)

This package does NOT include:
  • ❌ JavaScript built-in APIs (use @global, @math, @date, etc.)
  • ❌ Web APIs (use @web/* packages)
  • ❌ Runtime-specific APIs (use @node/*, @browser/*, etc.)

#Core Types

#Any - Universal JavaScript Value

The fundamental type for all JavaScript values:

pub type Any

// Convert MoonBit value to JS
let js_val : @core.Any = @core.any(42)
let js_str : @core.Any = @core.any("hello")

// Cast back to MoonBit type
let num : Int = js_val.cast()
let str : String = js_str.cast()

#Nullable[T] - Nullable Type

Represents values that can be null:

pub type Nullable[T]

// JavaScript value that might be null
let maybe_null : @core.Nullable[Int] = get_nullable_value()

match maybe_null {
Null => @console.log("Got null")
NotNull(value) => @console.log(value)
}

#Nullish[T] - Nullish Type

Represents values that can be null or undefined:

pub type Nullish[T]

// JavaScript value that might be null or undefined
let maybe_nullish : @core.Nullish[String] = get_nullish_value()

match maybe_nullish {
Nullish => @console.log("Got null or undefined")
NotNullish(value) => @console.log(value)
}

#Promise[T] - Promise Type

JavaScript Promise wrapper:

pub type Promise[T]

// Create promises
let p = @core.Promise::resolve(42)
let q = @core.Promise::reject("error")

// Wait in async context
async fn example() -> Int {
let value = p.wait()
value
}

#Type Casting Functions

#identity[A, B](value: A) -> B

Zero-cost type casting (unsafe, use with caution):

// Cast between types without conversion
let js_num : @core.Any = get_js_value()
let num : Int = @core.identity(js_num)

#any[T](value: T) -> Any

Convert MoonBit value to JavaScript value:

let js_val = @core.any(42)
let js_str = @core.any("hello")
let js_bool = @core.any(true)

#cast[A, B](value: A) -> B

Type-safe casting with .cast() method:

let js_val : @core.Any = @core.any(42)
let num : Int = js_val.cast() // Recommended

#Type Checking

// Check JavaScript type
@core.typeof_(value) // "number", "string", "boolean", etc.

// Check if value is instance of constructor
@core.instanceof_(value, constructor)

// Null/undefined checks
@core.is_null(value)
@core.is_undefined(value)
@core.is_nullish(value)

// Type checks
@core.is_number(value)
@core.is_string(value)
@core.is_boolean(value)
@core.is_object(value)
@core.is_array(value)
@core.is_function(value)

#Property Access

// Get property
let name = obj._get("name")

// Set property
obj._set("name", @core.any("Alice"))

// Index access (bracket notation)
obj["key"] = @core.any(value)
let val = obj["key"]

#Method Calls

// Call method
let result = obj._call("toString", [])
let result = obj._call("method", [@core.any(arg1), @core.any(arg2)])

// Invoke function
let fn = get_function()
let result = fn._invoke([@core.any(1), @core.any(2)])

#Object Creation

// Create empty object
let obj = @core.new_object()
obj["name"] = @core.any("Alice")
obj["age"] = @core.any(30)

// Create empty array
let arr = @core.new_array()

// Create object with constructor
let date = @core.new(date_constructor, [@core.any(2025), @core.any(11), @core.any(6)])

#Function Conversion

Convert MoonBit functions to JavaScript functions:

// 0 arguments
let js_fn = @core.from_fn0(fn() -> String { "hello" })

// 1 argument
let js_fn = @core.from_fn1(fn(x: Int) -> Int { x * 2 })

// 2 arguments
let js_fn = @core.from_fn2(fn(x: Int, y: Int) -> Int { x + y })

// 3 arguments
let js_fn = @core.from_fn3(fn(x: Int, y: Int, z: Int) -> Int { x + y + z })

#Promise Utilities

// Promisify async functions
let promise_fn = @core.promisify0(async fn() -> Int { 42 })
let promise_fn = @core.promisify1(async fn(x: Int) -> Int { x * 2 })

// Run async code
@core.run_async(async fn() {
let value = some_promise.wait()
@console.log(value)
})

// Suspend current async context
@core.suspend(fn(resolve, reject) {
// Async operation
resolve(@core.any(42))
})

#Error Handling

// Try-catch for JavaScript exceptions
@core.throwable(fn() {
potentially_throwing_code()
}) catch {
err => @console.log("Caught error")
}

#Logging

// Console log (for debugging)
@core.log("Debug message")
@core.log(some_value)

#Design Principles

  1. Minimal: Only essential interop utilities
  2. Zero-cost: Direct FFI bindings without overhead
  3. Type-safe: Leverage MoonBit's type system where possible
  4. Composable: Build higher-level abstractions on top

#See Also

  • Built-in APIs: Use @global, @math, @date, etc. from mizchi/js
  • Web APIs: Use @web/* packages for fetch, WebSocket, etc.
  • Runtime APIs: Use @node/*, @browser/*, @deno

#Performance Note

This package is designed for minimal overhead. Most operations compile to direct JavaScript operations without runtime checks or conversions. For detailed performance characteristics, see docs/runtime-cost.md.

#
ToAny

pub trait ToAny {
fn to_any(Self) -> Any
}

Trait for converting values to JavaScript Any type.

Provides a type-safe alternative to the @core.any() function. All implementations use zero-cost %identity conversion.

Usage:
fn[T : ToAny] set_value(obj : Any, key : String, value : T) -> Unit {
obj._set(key, ToAny::to_any(value))
}

Trade-off: Using fn[T : ToAny] causes monomorphization (code duplication per type). For smaller bundle size, either:
  • Use |> @core.any directly (no trait overhead)
  • Use &ToAny trait objects (dynamic dispatch, single function)
impl ToAny for Unit
impl ToAny for Bool
impl ToAny for Int
impl ToAny for Int64
impl ToAny for UInt
impl ToAny for UInt64
impl ToAny for Float
impl ToAny for Double
impl ToAny for String
impl ToAny for Bytes
impl ToAny for Array[T]

#
JsError

pub suberror JsError {
JsError(String)
}

Generic JS error type

#
ThrowError

pub suberror ThrowError {
ThrowError(Any)
}

impl Show for ThrowError

#
Any

#external
pub type Any

Opaque type for JavaScript values
impl ToAny for Any
impl Eq for Any
impl Show for Any

#
Any::_call

fn Any::_call(self : Any, key : String, args : Array[Any]) -> Any

Call method: obj._call("method", [arg1, arg2])

#
Any::_get

#alias("_[_]")
fn Any::_get(self : Any, key : String) -> Any

Get property: obj[key]

#
Any::_get_by_index

fn Any::_get_by_index(self : Any, key : Int) -> Any

Get by index: obj[index]

#
Any::_invoke

fn Any::_invoke(self : Any, args : Array[Any]) -> Any

Call function: func._invoke([arg1, arg2])

#
Any::_set

#alias("_[_]=_")
fn Any::_set(self : Any, key : String, value : Any) -> Unit

Set property: obj[key] = value

#
Any::cast

fn[T] Any::cast(self : Any) -> T

#
Any::to_string

fn Any::to_string(self : Any) -> String

#
Nullable

#external
pub type Nullable[T]

null or T

#
Nullable::is_null

fn[T] Nullable::is_null(self : Nullable[T]) -> Bool

#
Nullable::to_option

fn[T] Nullable::to_option(self : Nullable[T]) -> T?

#
Nullish

#external
pub type Nullish[T]

null | undefined | T

#
Nullish::to_option

fn[T] Nullish::to_option(self : Nullish[T]) -> T?

#
Promise

#external
pub type Promise[T]

JavaScript Promise[T] https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

#
Promise::all

fn[A] Promise::all(promises : Array[Promise[A]]) -> Promise[Array[A]]

JS: Promise.all(promises) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
let results = Promise::all([
from_async(async fn() { fetch_user() }),
from_async(async fn() { fetch_posts() }),
]).wait()

#
Promise::allSettled

fn[A] Promise::allSettled(promises : Array[Promise[A]]) -> Promise[Array[SettledResult[A]]]

JS: Promise.allSettled(promises) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
let results = Promise::allSettled([
from_async(async fn() { 1 }),
from_async(async fn() { fail("error") }),
]).wait()
// results[0].status == "fulfilled", results[0].value == Some(1)
// results[1].status == "rejected", results[1].reason != null

#
Promise::any

fn[T] Promise::any(promises : Array[Promise[T]]) -> Promise[T]

JS: Promise.any(promises) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
let first_success = Promise::any([
from_async(async fn() { fetch_from_mirror1() }),
from_async(async fn() { fetch_from_mirror2() }),
]).wait()

#
Promise::catch_

fn[A, B] Promise::catch_(self : Promise[A], f : (Error) -> Promise[B]) -> Promise[B]

#
Promise::finally_

fn[A] Promise::finally_(self : Promise[A], f : () -> Unit) -> Promise[A]

JS: promise.finally(() => { ... })

#
Promise::new

fn[A] Promise::new(f : async ((A) -> Unit, (Error) -> Unit) -> Unit) -> Promise[A]

JS: new Promise((resolve, reject) => { ... }) Create a Promise from an async executor function with resolve/reject callbacks.
let p : Promise[Int] = Promise::new(async fn(resolve, _reject) {
let result = some_async_operation()
resolve(result)
})

#
Promise::race

fn[T] Promise::race(promises : Array[Promise[T]]) -> Promise[T]

JS: Promise.race(promises) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
let first = Promise::race([
from_async(async fn() {
sleep(100)
"slow"
}),
from_async(async fn() {
sleep(10)
"fast"
}),
]).wait() // "fast"

#
Promise::reject

fn Promise::reject(x : Any) -> Promise[Any]

js: Promise.reject(error) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject

#
Promise::resolve

fn[A] Promise::resolve(x : A) -> Promise[A]

js: Promise.resolve(value) https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve

#
Promise::then

fn[A, B] Promise::then(self : Promise[A], resolve : (A) -> Promise[B] raise) -> Promise[B]

#
Promise::to_any

fn[T] Promise::to_any(self : Promise[T]) -> Any

#
Promise::wait

async fn[T] Promise::wait(self : Promise[T]) -> T

JS: await promise Waits for the Promise to be resolved or rejected.

#
Promise::withResolvers

fn[T] Promise::withResolvers() -> PromiseResolvers[T]

JS: Promise.withResolvers() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/withPromiseResolvers

#
PromiseResolvers

pub(all) struct PromiseResolvers[T] {
promise : Promise[T]
resolve : (T) -> Unit
reject : (Error) -> Unit
}

JS: Promise.withResolvers() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/withPromiseResolvers

#
PromiseResolvers::reject

fn[T] PromiseResolvers::reject(self : PromiseResolvers[T], error : Error) -> Unit

JS: resolvers.reject(error)

#
PromiseResolvers::resolve

fn[T] PromiseResolvers::resolve(self : PromiseResolvers[T], value : T) -> Unit

JS: resolvers.resolve(value)

#
SettledResult

pub(all) struct SettledResult[T] {
status : String
value : T?
reason : Nullable[Any]
}

Result type for Promise.allSettled

#
SettledResult::to_any

fn[T] SettledResult::to_any(self : SettledResult[T]) -> Any

#
TemplateStringsArray

#external
pub type TemplateStringsArray

TemplateStringsArray - corresponds to TypeScript's TemplateStringsArray.

In JavaScript tagged templates, the first argument is an array of string literals with a raw property containing the raw (unescaped) versions.

TypeScript definition:
interface TemplateStringsArray extends ReadonlyArray<string> { readonly raw: ReadonlyArray<string>; }

See: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html

#
TemplateStringsArray::get

#alias("_[_]")
fn TemplateStringsArray::get(self : TemplateStringsArray, index : Int) -> String

Get the string at the given index

#
TemplateStringsArray::length

fn TemplateStringsArray::length(self : TemplateStringsArray) -> Int

Get the length of the template strings array

#
TemplateStringsArray::raw

Get the raw array

#
TemplateStringsArray::raw_get

fn TemplateStringsArray::raw_get(self : TemplateStringsArray, index : Int) -> String

Get the raw (unescaped) string at the given index

#
TemplateStringsArray::to_any

#
TypeOf

#external
pub type TypeOf[T]

Represents a JavaScript constructor/class type. Similar to TypeScript's typeof ClassName.

#
TypeOf::as_any

fn[T] TypeOf::as_any(self : TypeOf[T]) -> Any

Convert TypeOf[T] to Any for use with generic functions

#
TypeOf::is_instanceof

fn[T] TypeOf::is_instanceof(self : TypeOf[T], v : Any) -> Bool

Type-safe instanceof check: TypeOf[T].is_instanceof(value)

#
Union2

#external
pub type Union2[A, B]

TS: A | B

#
Union2::from

fn[A, B] Union2::from(x : Any) -> Union2[A, B]

#
Union2::to0

fn[A, B] Union2::to0(self : Union2[A, B]) -> A

#
Union2::to1

fn[A, B] Union2::to1(self : Union2[A, B]) -> B

#
Union2::to_any

fn[A, B] Union2::to_any(self : Union2[A, B]) -> Any

#
Union3

#external
pub type Union3[A, B, C]

TS: A | B | C

#
Union3::from

fn[A, B, C] Union3::from(x : Any) -> Union3[A, B, C]

#
Union3::to0

fn[A, B, C] Union3::to0(self : Union3[A, B, C]) -> A

#
Union3::to1

fn[A, B, C] Union3::to1(self : Union3[A, B, C]) -> B

#
Union3::to2

fn[A, B, C] Union3::to2(self : Union3[A, B, C]) -> C

TS: A | B | C

#
Union3::to_any

fn[A, B, C] Union3::to_any(self : Union3[A, B, C]) -> Any

#
Union4

#external
pub type Union4[A, B, C, D]

TS: A | B | C | D

#
Union4::from

fn[A, B, C, D] Union4::from(x : Any) -> Union4[A, B, C, D]

#
Union4::to0

fn[A, B, C, D] Union4::to0(self : Union4[A, B, C, D]) -> A

#
Union4::to1

fn[A, B, C, D] Union4::to1(self : Union4[A, B, C, D]) -> B

#
Union4::to2

fn[A, B, C, D] Union4::to2(self : Union4[A, B, C, D]) -> C

#
Union4::to3

fn[A, B, C, D] Union4::to3(self : Union4[A, B, C, D]) -> D

#
Union4::to_any

fn[A, B, C, D] Union4::to_any(self : Union4[A, B, C, D]) -> Any

#
Union5

#external
pub type Union5[A, B, C, D, E]

TS: A | B | C | D | E

#
Union5::from

fn[A, B, C, D, E] Union5::from(x : Any) -> Union5[A, B, C, D, E]

#
Union5::to0

fn[A, B, C, D, E] Union5::to0(self : Union5[A, B, C, D, E]) -> A

#
Union5::to1

fn[A, B, C, D, E] Union5::to1(self : Union5[A, B, C, D, E]) -> B

#
Union5::to2

fn[A, B, C, D, E] Union5::to2(self : Union5[A, B, C, D, E]) -> C

#
Union5::to3

fn[A, B, C, D, E] Union5::to3(self : Union5[A, B, C, D, E]) -> D

#
Union5::to4

fn[A, B, C, D, E] Union5::to4(self : Union5[A, B, C, D, E]) -> E

#
Union5::to_any

fn[A, B, C, D, E] Union5::to_any(self : Union5[A, B, C, D, E]) -> Any

#
any

fn[T] any(value : T) -> Any

#
array_from

fn array_from(v : Any) -> Array[Any]

#
as_any

fn as_any(opt : Any?) -> Any

#
assert_throws

fn assert_throws(op : () -> Any) -> String raise JsError

Test helper: assert that op throws a JS exception Returns the error message if thrown, fails if no exception

#
assert_throws_with

fn assert_throws_with(op : () -> Any, expected_msg : String) -> Unit

Test helper: assert that op throws with specific message (contains check)

#
dispose

fn dispose(value : Any) -> Unit

#
dispose_async

async fn dispose_async(value : Any) -> Unit

Call async dispose (Symbol.asyncDispose) Usage: defer value |> dispose_async

#
dispose_async_ignore

fn dispose_async_ignore(value : Any) -> Unit

Call asyncDispose but ignore the result now moonbit can not async in defer Usage: defer browser |> dispose_async_ignore

#
equal

fn equal(a : Any, b : Any) -> Bool

#
export_sync

fn[E : Show + Error] export_sync(op : () -> Any raise E) -> Any

Wrap a MoonBit operation that may raise, converting errors to JS exceptions Usage: export_sync(fn() { may_raise_error() })

#
ffi_wrap_sync

fn ffi_wrap_sync(op : () -> Any, on_ok : (Any) -> Unit, on_error : (Any) -> Unit) -> Unit

JS operation wrapper with try-catch

#
from_async

fn[A] from_async(f : async () -> A) -> Promise[A]

Convert an async function to a Promise.
let p : Promise[Int] = from_async(async fn() {
let result = some_async_operation()
result
})

#
from_entries

fn from_entries(entries : Array[(String, Any)]) -> Any

#
from_fn0

fn[A] from_fn0(f : () -> A) -> Any

Wrap MoonBit function as JS function (0 args)

#
from_fn1

fn[A, B] from_fn1(f : (A) -> B) -> Any

Wrap MoonBit function as JS function (1 arg)

#
from_fn2

fn[A, B, C] from_fn2(f : (A, B) -> C) -> Any

Wrap MoonBit function as JS function (2 args)

#
from_fn3

fn[A, B, C, D] from_fn3(f : (A, B, C) -> D) -> Any

Wrap MoonBit function as JS function (3 args)

#
from_option

fn[A] from_option(opt : A?) -> Any

Convert an Option to Any, mapping None to null and Some(v) to v.

#
global_this

fn global_this() -> Any

#
identity

fn[A, B] identity(value : A) -> B

#
identity_option

fn[T] identity_option(v : Any) -> T?

Safely convert a JavaScript value to an Option type. Converts JavaScript null or undefined to None, otherwise returns Some(value).

#
instanceof_

fn instanceof_(v : Any, cls : Any) -> Bool

#
is_array

fn is_array(v : Any) -> Bool

#
is_null

fn is_null(v : Any) -> Bool

#
is_nullish

fn is_nullish(v : Any) -> Bool

#
is_object

fn is_object(v : Any) -> Bool

#
is_undefined

fn is_undefined(v : Any) -> Bool

#
json_parse

fn json_parse(text : String) -> Any

#
json_stringify

fn json_stringify(value : Any) -> String

#
json_stringify_pretty

fn json_stringify_pretty(value : Any, space : Int) -> String

#
log

fn log(message : Any) -> Unit

#
new

fn new(cls : Any, args : Array[Any]) -> Any

JS: new cls(...args)

#
new_array

fn new_array() -> Any

#
new_instance

fn[T] new_instance(cls : TypeOf[T], args : Array[Any]) -> T

Type-safe constructor call: new cls(...args) -> T

#
new_object

fn new_object() -> Any

#
null

fn null() -> Any

#
nullable

fn[T] nullable(opt : T?) -> Any

Convert Option[T] to Any (None becomes undefined) Zero-cost: concrete Option[T] compiles to T | undefined in JS

#
object_assign

fn object_assign(target : Any, source : Any) -> Any

#
object_has_own

fn object_has_own(obj : Any, key : String) -> Bool

#
object_keys

fn object_keys(obj : Any) -> Array[String]

#
object_values

fn object_values(obj : Any) -> Array[Any]

#
promisify0

fn[R] promisify0(f : async () -> R) -> (() -> Promise[R])

#
promisify1

fn[A, R] promisify1(f : async (A) -> R) -> ((A) -> Promise[R])

Moonbit Async Function to JS Promise Function

#
promisify2

fn[A, B, R] promisify2(f : async (A, B) -> R) -> ((A, B) -> Promise[R])

Moonbit Async Function to JS Promise Function

#
promisify3

fn[A, B, C, R] promisify3(f : async (A, B, C) -> R) -> ((A, B, C) -> Promise[R])

Moonbit Async Function to JS Promise Function

#
run_async

fn run_async(f : async () -> Unit noraise) -> Unit

MoonBit builtin %async.run

#
sleep

async fn sleep(ms : Int) -> Unit noraise

#
suspend

async fn[T, E : Error] suspend(f : ((T) -> Unit, (E) -> Unit) -> Unit) -> T raise E

MoonBit builtin %async.suspend

#
tag

fn[T] tag(tag_fn : (TemplateStringsArray, Array[Any]) -> T, template : String, args : Array[Any]) -> T

Call a JavaScript tagged template function with type safety.

This function enables MoonBit code to call JavaScript tagged template literals like css\color: ${color}`` or html\${content}``.

Template Format

Use ${@0}, ${@1}, etc. as placeholders (0-indexed):
  • "color: ${@0}; font-size: ${@1}px" with args ["red", "16"]

Tag Function Type

The tag function should have the signature:
fn(TemplateStringsArray, Array[Any]) -> T

TypeScript equivalent:
type TagFn<T> = (strings: TemplateStringsArray, ...values: any[]) => T

Example

// Define a typed tag function
fn my_css(
strings : @core.TemplateStringsArray,
values : Array[@core.Any],
) -> String {
let mut result = strings[0]
for i, v in values {
result = result + v.to_string() + strings[i + 1]
}
result
}

// Call it

let result : String = tag(my_css, "color: ${@0}", [@core.any("red")])

How It Works

JavaScript tagged templates receive:
  1. An array of string parts (with a raw property)
  2. The interpolation values as separate arguments

For template "a${@0}b${@1}c" with args [1, 2]:
  • strings: ["a", "b", "c"] (with strings.raw = ["a", "b", "c"])
  • values: [1, 2]
  • Calls: tagFn(strings, 1, 2)

#
throw_

fn throw_(value : Any) -> Unit

#
throw_error

fn throw_error(msg : String) -> Unit

Throw a JS Error with the given message

#
throwable

fn[T] throwable(f : () -> T raise?) -> T raise ThrowError

Wraps a synchronous function call, converting any thrown JS errors into ThrowError
let result = throwable(() => undefined()._invoke([]))

#
try_sync

fn try_sync(op : () -> Any) -> Any raise JsError

Safe wrapper that converts JS exceptions to MoonBit errors

#
typeof_

fn typeof_(v : Any) -> String

#
undefined

fn undefined() -> Any