JavaScript and TypeScript

A Refingere program can call JavaScript and TypeScript, and JavaScript and TypeScript can call it. Either way it's done with ordinary functions marked @js - which way depends on whether the function has a body. The JavaScript itself lives in its own modules, written in JavaScript or TypeScript. Values are converted as they cross, so neither side sees the other's representation.

A @js declaration - no body - is a JavaScript function, implemented by the module it names: Refingere calls it. A @js definition - with a body - is a Refingere function JavaScript can call. See @js.

Calling a module

@js("./text.ts", "slugify") on a function without a body makes slug call the module's slugify export. The path is relative to the .rfn file; a package name such as node:path works too. Leaving out the second argument calls the export with the function's own name. The declared types say what crosses: the arguments are converted to JavaScript values, and the result back into the declared return type - so the lengths above come back as U64s, not as floats.

What crosses

RefingereJavaScript
F64, F32number
U8 ... U256, S8 ... S256number, or bigint past 253 - either comes back as the declared type - an OverflowError when it doesn't fit
String, Booleanstring, boolean
undefinedundefined - and null comes back as undefined
Array, Map, Setan array, a Map, a Set, item by item
BytesUint8Array
a struct an object of its fields - and an object comes back as the struct the function's return type names, field by field, or as a Map<String, Unknown>
an enum case its name as a string; with a payload, { variant, values }
JsValue anything else - a function, a class instance, a DOM node - held as it is and passed back unchanged

A JsValue can be kept, compared and handed to another JavaScript function, which gets the very same object - and Refingere can reach into it without writing any JavaScript: get and set its properties, call its methods, construct an instance of it, or await it with toPromise when it's a JavaScript promise (see The Browser). A Refingere function or lambda can't be passed to JavaScript as it is - JsValue.callback wraps one in a function JavaScript can call.

Errors

An exception thrown on the JavaScript side comes back as an Error with the exception's message, so it's handled like any other - with !!, ?? or match. Going the other way, a @js definition that ends with an error throws a JavaScript Error with its message.

Native libraries

A shared library written in C - or anything exporting C functions: Rust, Zig, Go - is called the same way, through a module that opens it with Bun's bun:ffi. The module says what C types each function takes and gives; the @js declaration says what Refingere sees.

Numbers and truth values cross as they are. A string goes to C as a pointer - ptr(Buffer.from(text + "\0")) in the module - and comes back with new CString(pointer); memory the library hands out stays the module's to free. It runs where Bun does - with --run and --test, or a program compiled with --js - but not in plain WebAssembly or a native executable, which have no JavaScript to call.

Without JavaScript

Compiled without --js, a module is plain WebAssembly (WASI): wasmtime runs it, so it can be a server or a command-line tool, and it will compile to a native binary the same way. There's no JavaScript there: a @js definition is an ordinary function, and a @js declaration panics if it's reached. --run and --test compile with --js whenever the program calls JavaScript, and run it on Bun.

Calling Refingere from JavaScript

A @js definition is callable from JavaScript, under its own name or the one given. --compile --js writes, next to the module, a loader (.mjs) with the JavaScript modules the program calls bundled in, and its TypeScript declarations (.d.mts). load() instantiates the module and returns an object with a method per @js definition - plus main() when the program has a @main, returning its exit status. It takes the same options a program run from the command line gets: args, env and the folders it may open. See @js and Command Line.

written by --compile --js

Each function's TypeScript type comes from its Refingere one, with the table above read the other way: a 64-bit or wider integer is number | bigint, a struct an object type of its fields, and an Error ! T result just T, since the error is thrown. A parameter with a default can be left out.

Compiled to JavaScript

With --target=js, the whole program is compiled to JavaScript rather than WebAssembly: one ES module, with the JavaScript and TypeScript it calls imported - and, written with --compile, bundled in. Its @js definitions are the module's own exports, so there's nothing to load first, and run() runs its @main (or, built with --test, every @test), taking the same options as load() above and giving the exit status. Run as a script - node words.mjs - it runs @main by itself. Values cross the same way, by the table above, and the program behaves the same as it does compiled to WebAssembly. See Compiling to JavaScript.

written by --compile --target=js

In a web page

--compile --js=browser writes a loader for a web page: its load() is async, fetching and compiling the module next to it, and the program's &stdout and &stderr go to the browser's console. With both directions, the page and the program work together: the script calls update on every keystroke, and update writes into the page through the @js declarations - a DOM element crosses as a JsValue, and comes back to JavaScript as the very same element. The repository's examples/js-website is a complete page built this way, with its build script.