Skip to main content
Version: Next

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.