Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

**WARNING**: This project is in its alpha phase and not yet production-ready. Use it with care!

`js-ffi` is a library allowing you to interact with basic JavaScript constructs from MoonBit.
`js-ffi` is a library allowing you to interact with basic JavaScript constructs from MoonBit,
ensuring as much null-safety as possible.
14 changes: 7 additions & 7 deletions src/js/async.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extern "js" fn Promise::wait_ffi(

///|
pub async fn Promise::wait(self : Promise) -> Value! {
suspend!!(fn(k, ke) { Promise::wait_ffi(self, k, fn(e) { ke(Error_(e)) }) })
suspend!(fn(k, ke) { Promise::wait_ffi(self, k, fn(e) { ke(Error_(e)) }) })
}

///|
Expand All @@ -39,7 +39,7 @@ pub async fn Promise::wait(self : Promise) -> Value! {
///
/// If you don't care about the result of the operation, you can use `spawn_detach` instead.
pub fn Promise::unsafe_new[T](op : async () -> T!) -> Promise {
Promise::new_ffi(fn() { Value::cast_from(op!!()) })
Promise::new_ffi(fn() { Value::from(op!()) })
}

///|
Expand All @@ -50,7 +50,7 @@ extern "js" fn Promise::new_ffi(op : async () -> Value!) -> Promise =
pub fn spawn_detach[T, E : Error](op : async () -> T!E) -> Unit {
async_run(fn() {
try {
op!!() |> ignore
op!() |> ignore
} catch {
_ => ()
}
Expand All @@ -65,21 +65,21 @@ pub extern "js" fn Promise::all(promises : Array[Promise]) -> Promise = "(ps) =>
///|
/// Wraps each given `async fn` in a `Promise` and waits for all of them to resolve.
pub async fn async_all![T](ops : Array[async () -> T!]) -> Array[T] {
async_all_raw!!(ops.map(fn(op) { async fn!() { Value::cast_from(op!!()) } })).map(
Value::cast,
async_all_raw!(ops.map(fn(op) { async fn!() { Value::from(op!()) } })).map(
Value::unsafe_into,
)
}

///|
async fn async_all_raw!(ops : Array[async () -> Value!]) -> Array[Value] {
Promise::all(ops.map(Promise::unsafe_new)).wait!!().cast()
Promise::all(ops.map(Promise::unsafe_new)).wait!().unsafe_into()
}

///|
pub fn async_test(op : async () -> Unit!) -> Unit {
async_run(async fn() {
try {
op!!()
op!()
} catch {
e => {
println("ERROR in `async_test`: \{e}")
Expand Down
4 changes: 2 additions & 2 deletions src/js/async_deprecated.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ extern "js" fn async_wrap_ffi(
///|
#deprecated("use Promise::wait instead")
pub async fn async_wrap(op : AsyncOp) -> Value! {
suspend!!(fn(k, ke) { async_wrap_ffi(op, k, fn(e) { ke(Error_(e)) }) })
suspend!(fn(k, ke) { async_wrap_ffi(op, k, fn(e) { ke(Error_(e)) }) })
}

///|
#deprecated("use Promise::new instead")
pub fn async_unwrap[T](op : async () -> T!) -> Promise {
Promise::new_ffi(fn() { Value::cast_from(op!!()) })
Promise::new_ffi(fn() { Value::from(op!()) })
}

///|
Expand Down
6 changes: 3 additions & 3 deletions src/js/async_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ async fn op1() -> String? {

///|
async fn op2() -> String? {
guard op1!!() is Some(prefix)
guard op1!() is Some(prefix)
Some(prefix + ", World")
}

Expand All @@ -18,7 +18,7 @@ async fn op3() -> String? {
test "Promise::wait" {
@js.async_test(fn!() {
// Promise::unsafe_new + Promise::wait is a noop.
let res = @js.Promise::unsafe_new(fn() { op1!!() }).wait!!()
let res = @js.Promise::unsafe_new(fn() { op1!() }).wait!()
assert_eq!(res.cast(), Some("Hello"))
})
}
Expand All @@ -27,7 +27,7 @@ test "Promise::wait" {
test "async_all" {
@js.async_test(fn!() {
assert_eq!(
@js.async_all!!([fn() { op2!!() }, fn() { op1!!() }, fn() { op3!!() }]),
@js.async_all!([fn() { op2!() }, fn() { op1!() }, fn() { op3!() }]),
[Some("Hello, World"), Some("Hello"), None],
)
})
Expand Down
12 changes: 6 additions & 6 deletions src/js/cast.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub impl Cast for String with into(value) {

///|
pub impl Cast for String with from(value) {
Value::cast_from(value)
Value::from(value)
}

///|
Expand All @@ -56,7 +56,7 @@ pub impl Cast for Int with into(value) {

///|
pub impl Cast for Int with from(value) {
Value::cast_from(value)
Value::from(value)
}

///|
Expand All @@ -66,7 +66,7 @@ pub impl Cast for Double with into(value) {

///|
pub impl Cast for Double with from(value) {
Value::cast_from(value)
Value::from(value)
}

///|
Expand All @@ -76,7 +76,7 @@ pub impl Cast for Bool with into(value) {

///|
pub impl Cast for Bool with from(value) {
Value::cast_from(value)
Value::from(value)
}

///|
Expand All @@ -86,7 +86,7 @@ pub impl[A : Cast] Cast for Array[A] with into(value) {
.bind(fn(arr) {
let is_type_a = fn(elem) { not((Cast::into(elem) : A?).is_empty()) }
if arr.iter().all(is_type_a) {
Some(Value::cast_from(arr).cast())
Some(Value::from(arr).unsafe_into())
} else {
None
}
Expand All @@ -95,5 +95,5 @@ pub impl[A : Cast] Cast for Array[A] with into(value) {

///|
pub impl[A : Cast] Cast for Array[A] with from(value) {
Value::cast_from(value)
Value::from(value)
}
4 changes: 2 additions & 2 deletions src/js/error.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn Error_::cause(self : Error_) -> Value? {
let Error_(inner) = self
let cause = Error_::cause_ffi(inner)
guard not(cause.is_undefined()) else { None }
Some(cause.cast())
Some(cause.unsafe_into())
}

///|
Expand All @@ -33,7 +33,7 @@ extern "js" fn Error_::wrap_ffi(
///|
pub fn Error_::wrap[T](
op : () -> Value,
map_ok~ : (Value) -> T = Value::cast
map_ok~ : (Value) -> T = Value::unsafe_into
) -> T!Error_ {
let mut res : Result[Value, Error_] = Ok(Value::undefined())
Error_::wrap_ffi(op, fn(v) { res = Ok(v) }, fn(e) { res = Err(Error_(e)) })
Expand Down
11 changes: 11 additions & 0 deletions src/js/js.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,13 @@ impl Show for Error_
type Nullable[_]
impl Nullable {
from_option[T](T?) -> Self[T]
#deprecated
get_exn[T](Self[T]) -> T
is_null[T](Self[T]) -> Bool
null[T]() -> Self[T]
to_option[T](Self[T]) -> T?
unsafe_get[T](Self[T]) -> T
unwrap[T](Self[T]) -> T
}

pub type Object Value
Expand All @@ -88,10 +91,13 @@ impl Object {
type Optional[_]
impl Optional {
from_option[T](T?) -> Self[T]
#deprecated
get_exn[T](Self[T]) -> T
is_undefined[T](Self[T]) -> Bool
to_option[T](Self[T]) -> T?
undefined[T]() -> Self[T]
unsafe_get[T](Self[T]) -> T
unwrap[T](Self[T]) -> T
}

pub extern type Promise
Expand Down Expand Up @@ -215,14 +221,18 @@ impl Value {
apply_with_index[Arg, Result](Self, Int, Array[Arg]) -> Result
apply_with_string[Arg, Result](Self, String, Array[Arg]) -> Result
apply_with_symbol[Arg, Result](Self, Symbol, Array[Arg]) -> Result
#deprecated
cast[T](Self) -> T
#deprecated
cast_from[T](T) -> Self
extends(Self, Self) -> Self
from[T](T) -> Self
from_json(Json) -> Self!
from_json_string(String) -> Self!
get_with_index[T](Self, Int) -> T
get_with_string[T](Self, String) -> T
get_with_symbol[T](Self, Symbol) -> T
into[T](Self) -> T
is_bool(Self) -> Bool
is_null(Self) -> Bool
is_number(Self) -> Bool
Expand All @@ -240,6 +250,7 @@ impl Value {
to_json(Self) -> Json!
to_json_string(Self) -> String!
to_string(Self) -> String
unsafe_into[T](Self) -> T
}
impl Show for Value
impl @moonbitlang/core/json.FromJson for Value
Expand Down
29 changes: 23 additions & 6 deletions src/js/null.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,41 @@ extern type Nullable[_]

///|
pub fn Nullable::is_null[T](self : Nullable[T]) -> Bool {
Value::is_null(Value::cast_from(self))
Value::is_null(Value::from(self))
}

///|
#deprecated("get_exn is not null-safety. Use `unwrap` or `unsafe_get` instead.")
pub fn Nullable::get_exn[T](self : Nullable[T]) -> T = "%identity"

///|
///| Get the underlying value without checking for null.
///
/// This is unsafe and should only be used when you are sure that the value is not null.
/// It's recommended to use `unwrap` instead.
pub fn Nullable::unsafe_get[T](self : Nullable[T]) -> T = "%identity"

///| Get the underlying value with checking for null.
///
/// If the value is null, it will abort the program.
pub fn Nullable::unwrap[T](self : Nullable[T]) -> T {
if self.is_null() {
abort("unwrap on null")
}
self.unsafe_get()
}

///| Convert a Nullable value to an Option.
pub fn Nullable::to_option[T](self : Nullable[T]) -> T? {
guard not(Value::cast_from(self).is_null()) else { None }
Some(self.get_exn())
guard not(Value::from(self).is_null()) else { None }
Some(self.unsafe_get())
}

///|
pub fn Nullable::null[T]() -> Nullable[T] {
Value::null().cast()
Value::null().unsafe_into()
}

///|
pub fn Nullable::from_option[T](value : T?) -> Nullable[T] {
value.map(Value::cast_from).or_else(Value::null).cast()
value.map(Value::from).or_else(Value::null).unsafe_into()
}
4 changes: 2 additions & 2 deletions src/js/object.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ pub fn Object::from_iter2[K, V](it : Iter2[K, V]) -> Object {

///|
pub fn Object::op_get[K, V](self : Object, key : K) -> V {
self._.get_ffi(Value::cast_from(key)).cast()
self._.get_ffi(Value::from(key)).unsafe_into()
}

///|
pub fn Object::op_set[K, V](self : Object, key : K, value : V) -> Unit {
self._.set_ffi(Value::cast_from(key), Value::cast_from(value))
self._.set_ffi(Value::from(key), Value::from(value))
}
28 changes: 21 additions & 7 deletions src/js/optional.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,44 @@ extern type Optional[_]

///|
pub fn Optional::is_undefined[T](self : Optional[T]) -> Bool {
self |> Value::cast_from |> Value::is_undefined
self |> Value::from |> Value::is_undefined
}

///|
///|
#deprecated("get_exn is not undefined-safety. Use `unwrap` or `to_option` instead.")
pub fn Optional::get_exn[T](self : Optional[T]) -> T = "%identity"

///|
///| Get the underlying value without checking for undefined.
pub fn Optional::unsafe_get[T](self : Optional[T]) -> T = "%identity"

///| Get the underlying value with checking for undefined.
///
/// If the value is undefined, it will abort the program.
pub fn Optional::unwrap[T](self : Optional[T]) -> T {
if self.is_undefined() {
abort("unwrap on undefined")
}
self.unsafe_get()
}

///| Convert an Optional value to an Option.
pub fn Optional::to_option[T](self : Optional[T]) -> T? {
if Value::cast_from(self).is_undefined() {
if Value::from(self).is_undefined() {
None
} else {
Some(self.get_exn())
Some(self.unsafe_get())
}
}

///|
pub fn Optional::undefined[T]() -> Optional[T] {
Value::undefined().cast()
Value::undefined().unsafe_into()
}

///|
pub fn Optional::from_option[T](value : T?) -> Optional[T] {
match value {
Some(v) => Value::cast_from(v).cast()
Some(v) => Value::from(v).unsafe_into()
None => Optional::undefined()
}
}
2 changes: 1 addition & 1 deletion src/js/require.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ extern "js" fn require_ffi(path : String) -> Value = "(path) => require(path)"
///|
pub fn require(path : String, keys~ : Array[String] = []) -> Value {
keys.fold(init=require_ffi(path), fn {
acc, key => get_ffi(acc, Value::cast_from(key))
acc, key => get_ffi(acc, Value::from(key))
})
}
10 changes: 5 additions & 5 deletions src/js/symbol.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ extern type Symbol

///|
pub fn Symbol::make() -> Symbol {
Symbol::make_ffi(Value::undefined()).cast()
Symbol::make_ffi(Value::undefined()).unsafe_into()
}

///|
pub fn Symbol::make_with_string_js(value : String) -> Symbol {
Symbol::make_ffi(Value::cast_from(value)).cast()
Symbol::make_ffi(Value::from(value)).unsafe_into()
}

///|
Expand All @@ -18,7 +18,7 @@ pub fn Symbol::make_with_string(value : String) -> Symbol {

///|
pub fn Symbol::make_with_number(num : Double) -> Symbol {
Symbol::make_ffi(Value::cast_from(num)).cast()
Symbol::make_ffi(Value::from(num)).unsafe_into()
}

///|
Expand All @@ -30,11 +30,11 @@ extern "js" fn Symbol::iterator_ffi() -> Value =
#| () => Symbol.iterator

///|
pub let iterator : Symbol = Symbol::iterator_ffi().cast()
pub let iterator : Symbol = Symbol::iterator_ffi().unsafe_into()

///|
extern "js" fn Symbol::async_iterator_ffi() -> Value =
#| () => Symbol.asyncIterator

///|
pub let async_iterator : Symbol = Symbol::async_iterator_ffi().cast()
pub let async_iterator : Symbol = Symbol::async_iterator_ffi().unsafe_into()
Loading