Kite

js

! The host, reached generally. Web only. ! ! This is the floor below the typed world, and importing it should be visible ! in a file's first three lines. Everything here is untyped, every call can ! fail, and nothing here knows what a DOM is. std/dom is written over it, in ! ordinary Kite, and so is anything else a program needs that the standard ! library never covered. ! ! About twenty primitives, and they are general enough to reach anything the ! host has. std/dom is written over them, and so is whatever a program ! needs that nobody wrapped — in Kite, without touching the compiler, and with ! the generated glue staying a fixed size however much of the platform a ! program uses. ! ! The name is looked up when the program runs rather than fixed when it ! compiles. That costs a little speed, and it means a mistyped name is found ! at run time — which is why every call here answers with an error rather than ! throwing. ! ! ``kite ignore ! use std/js ! ! fn title() -> (str, error) { ! let document = js.global("document") ! let (value, err) = js.get(document, "title") ! check err ! return js.as_str(value) ! } ! ` ! ! **Keep JsValue out of your own public interface.** Wrap it in a struct ! whose field is not pub — outside the declaring module such a struct can be ! held and passed but not read or built ([spec §4.2]), which is the whole of ! how the untyped layer is stopped from spreading. std/dom` is the worked ! example.

SAFE_INTEGER

pub fn SAFE_INTEGER() -> int

The largest whole number a JavaScript number holds exactly.

Numbers cross as f64, because that is what a JavaScript number is and an int is an i64 — every crossing would otherwise allocate a BigInt. The cost is this limit, and it is checked here rather than discovered as a value that came back different from the one that went out.

global

pub fn global(name: str) -> JsValue

A property of the global object: document, window, Math, a constructor. The root everything else is reached from.

Not fallible: reading a property that is not there is not an error in JavaScript, it is undefined. Ask [is_nothing] if that matters.

nothing

pub fn nothing() -> JsValue

null, for passing to something that wants one.

get

pub fn get(target: JsValue, name: str) -> (JsValue, error)

One property.

set

pub fn set(target: JsValue, name: str, value: JsValue) -> error

Write one property.

Returns only an error, because there is no value to return and a (unit, error) pair would make every call site destructure something meaningless.

at

pub fn at(target: JsValue, index: int) -> (JsValue, error)

One element of an array-like thing.

length

pub fn length(target: JsValue) -> (int, error)

How many elements an array-like thing has.

call0

pub fn call0(target: JsValue, name: str) -> (JsValue, error)

call1

pub fn call1(target: JsValue, name: str, a: JsValue) -> (JsValue, error)

call2

pub fn call2(target: JsValue, name: str, a: JsValue, b: JsValue) -> (JsValue, error)

call3

pub fn call3(
    target: JsValue,
    name: str,
    a: JsValue,
    b: JsValue,
    c: JsValue,
) -> (JsValue, error)

call4

pub fn call4(
    target: JsValue,
    name: str,
    a: JsValue,
    b: JsValue,
    c: JsValue,
    d: JsValue,
) -> (JsValue, error)

new0

pub fn new0(constructor: str) -> (JsValue, error)

new Thing(…), by the constructor's name on the global object.

new1

pub fn new1(constructor: str, a: JsValue) -> (JsValue, error)

new2

pub fn new2(constructor: str, a: JsValue, b: JsValue) -> (JsValue, error)

new3

pub fn new3(constructor: str, a: JsValue, b: JsValue, c: JsValue) -> (JsValue, error)

same

pub fn same(a: JsValue, b: JsValue) -> bool

Whether two references are the same object — the host's ===.

This is what == would have been, and cannot be: an externref is outside Wasm's eq hierarchy, so there is no comparison to lower it to. Writing == on a JsValue is a compile error that points here.

is_nothing

pub fn is_nothing(v: JsValue) -> bool

Whether the value is null or undefined.

One question rather than two. The difference between them is a JavaScript distinction with no Kite meaning, and a program that acted on it would be acting on which of two ways a property failed to be there.

kind_of

pub fn kind_of(v: JsValue) -> str

The host's own answer to what this is: "string", "number", "object", "function", "undefined".

instance_of

pub fn instance_of(v: JsValue, constructor: str) -> (bool, error)

Whether the value came from this constructor.

Needed the moment an event target has to become something typed: a target may be an element, a text node or the window, and only one of those is worth wrapping.

of_str

pub fn of_str(value: str) -> JsValue

of_num

pub fn of_num(value: float) -> JsValue

of_bool

pub fn of_bool(value: bool) -> JsValue

of_int

pub fn of_int(value: int) -> (JsValue, error)

An int as a JavaScript number, refusing rather than rounding.

The check is on this side and the conversion is on the other. Doing the arithmetic here would mean an int becoming a float in Kite, which is a lossy cast the compiler rightly warns about — and the loss it warns about is exactly the one this refuses.

as_str

pub fn as_str(v: JsValue) -> (str, error)

The value as text — and an error when it is not text.

This is where JavaScript's commonest bug is removed. Reading a property that is not there yields undefined, and undefined silently becoming 0 or "" is a mistake that surfaces somewhere else entirely. Here it is a value the taint analysis will not let the caller use unchecked.

as_num

pub fn as_num(v: JsValue) -> (float, error)

as_bool

pub fn as_bool(v: JsValue) -> (bool, error)

as_int

pub fn as_int(v: JsValue) -> (int, error)

A whole number, or the reason it is not one.

"Is this number a whole one that survives the crossing" is JavaScript's own question — Number.isSafeInteger — and it is asked where the number lives rather than reconstructed here out of a truncation and a float comparison. The value only becomes an int once the answer is yes.

settle

pub fn settle(promise: JsValue, done: fn(JsValue), failed: fn(str)) -> error

Handle both halves of a promise.

Both callbacks are required, and that is the whole design. then with one callback compiles, runs, and throws a rejection away — which is exactly the failure this language spends its error design preventing everywhere else. Passing a failed that ignores its argument is still a decision somebody wrote down; a missing one is a decision nobody made.

The failure arrives as text rather than as an error, and that is deliberate. An error in Kite is either nil or a failure, so a handler taking one would have to test it for nil at every call site — for a callback that only ever runs when something went wrong. A parameter that can never be nil should not be typed as though it can. Anything wanting an error to propagate writes errors.new(why), which is all this layer would have done.

let (p, err) = js.call1(js.global("globalThis"), "fetch", js.of_str(url))
check err
check js.settle(p,
    |response: JsValue| { show(response) },
    |why: str| { io.error("could not load: \(why)") },
)

str_or

pub fn str_or(target: JsValue, name: str, fallback: str) -> str

A string property, or fallback when it is missing or is not a string.

num_or

pub fn num_or(target: JsValue, name: str, fallback: float) -> float

A numeric property, or fallback.

bool_or

pub fn bool_or(target: JsValue, name: str, fallback: bool) -> bool

A boolean property, or fallback.