Skip to main content
Version: 0.76.0

Fable and JavaScript Hosts

FScript can run in JavaScript hosts through the Fable-compiled @magnusopera/fscript package. The package targets Node and browser-compatible ESM environments and exposes the core language pipeline without the .NET runtime extern catalog.

Use this package when a JavaScript application needs to parse, type-check, evaluate, load, or invoke FScript code directly in-process.

Install​

npm install @magnusopera/fscript
import {
T,
extern,
run,
load,
invoke,
getValue,
createSession,
submit,
} from "@magnusopera/fscript";

Run a script​

run parses, infers, and evaluates a source string.

const result = run("let x = 41\nx + 1");

console.log(result);
// { kind: "int", value: 42n }

FScript int values are returned as JavaScript bigint. Safe JavaScript integer numbers are accepted as input arguments.

Load and invoke exports​

Use [<export>] on top-level bindings that the host should call repeatedly.

const loaded = load(`
[<export>] let add x y = x + y
[<export>] let answer = 42
`);

const sum = invoke(loaded, "add", [1, 2]);
const answer = getValue(loaded, "answer");

listFunctions(loaded) and listValues(loaded) return the exported callable and value names.

Register JavaScript externs​

Externs are host functions with explicit type schemes. Arguments are passed as tagged FScript values, and the return value must be a tagged value or a supported plain JavaScript value.

const shout = extern({
name: "Host.shout",
arity: 1,
scheme: T.scheme(T.func(T.string, T.string)),
invoke(args) {
return { kind: "string", value: `${args[0].value}!` };
},
});

const result = run('Host.shout "fable"', { externs: [shout] });

Resolve imports from virtual sources​

Browser and Node Fable hosts do not use the .NET file-backed resolver. Imports must resolve through virtual paths supplied by the host.

const loaded = load(
'import "shared.fss" as Shared\n[<export>] let value = Shared.inc 41',
{
rootDirectory: "/",
entryFile: "/main.fss",
sources: {
"/shared.fss": "let inc x = x + 1",
},
},
);

The host may also provide resolveImport(path) for lazy source lookup. Imported paths must stay under rootDirectory and end in .fss.

Use a browser session​

The JavaScript facade includes a small stateful session API for browser sandbox and REPL-like hosts. It retains declarations between submissions and returns a result only when the submission contains an expression.

const session = createSession({
rootDirectory: "/",
entryFile: "/main.fss",
});

submit(session, "let add x y = x + y");
const result = submit(session, "add 20 22");

console.log(result.text);
// "42"

resetSession(session) clears retained declarations.

Tagged values​

Values crossing the JavaScript boundary are represented structurally:

FScript valueJavaScript shape
(){ kind: "unit" }
42{ kind: "int", value: 42n }
3.14{ kind: "float", value: 3.14 }
true{ kind: "bool", value: true }
"text"{ kind: "string", value: "text" }
[1; 2]{ kind: "list", values: [...] }
(1, "x"){ kind: "tuple", values: [...] }
{ name = "Ada" }{ kind: "record", fields: { ... } }
Some 1 / None{ kind: "option", value: taggedValue | null }

Functions, externals, tasks, and union constructors are opaque at the JavaScript boundary. Exported functions should be called through invoke.

Errors​

Language errors are thrown as JavaScript Error objects with structured fields:

try {
run("let = 1");
} catch (error) {
console.log(error.kind); // "fscript-error"
console.log(error.phase); // "parse", "type", "eval", or "host"
console.log(error.span);
}

Current limits​

The Fable package exposes the core language engine, virtual imports, exported invocation, tagged values, sessions, and JavaScript-provided externs. It does not include .NET runtime externs such as Fs.*, Console.*, Task.spawn, or Task.await.

Try the browser runtime in the FScript sandbox.