---
title: "Getting started with OneEcma"
url: https://recursiveai.org/oneecma/docs/getting-started/
description: "Install OneEcma for .NET 10, evaluate JavaScript from C#, pass values and functions in, read the error report when a script fails, run untrusted code…"
version: "1.0.0"
---

# Getting started with OneEcma

Install OneEcma for .NET 10, evaluate JavaScript from C#, pass values and functions in, read the error report when a script fails, run untrusted code under limits, and check source without running it.

## Install

OneEcma targets `net10.0`, so you need the .NET 10 SDK. The packages share one version; this guide uses 1.0.0.

```shell
dotnet new console -o OneEcmaStart
cd OneEcmaStart
dotnet add package RecursiveAI.OneEcma --version 1.0.0
```

`RecursiveAI.OneEcma` is the whole engine, `Intl` included, with no dependency beyond the .NET base class library. It is NativeAOT- and trim-compatible. Two more packages are optional:

- `RecursiveAI.OneEcma.Generators` writes host bindings for .NET types marked `[JsExport]` at build time. See [Expose .NET objects with [JsExport]](https://recursiveai.org/oneecma/cookbook/jsexport/).
- `RecursiveAI.OneEcma.Cli` is the `oneecma` command: `dotnet tool install -g RecursiveAI.OneEcma.Cli`. See [The oneecma command](https://recursiveai.org/oneecma/cookbook/command-line/).

The engine has no file, network, process or environment access of its own. A script reaches only what your code hands it.

The snippets below are one program, in order. Put these `using` directives at the top of `Program.cs`:

```csharp
using RecursiveAI.OneEcma;
using RecursiveAI.OneEcma.Diagnostics;
using RecursiveAI.OneEcma.Syntax;
```

## Evaluate a script

`ScriptEngine` is one engine with one global scope. `Evaluate` runs a script and returns its completion value as a `JsValue`:

```csharp
var engine = new ScriptEngine();
JsValue result = engine.Evaluate("[1, 2, 3].map(n => n * 2).join('-')", "sum.js");
Console.WriteLine(engine.ToDisplayString(result));
Console.WriteLine(engine.Evaluate("6 * 7").AsNumber);
```

```text
2-4-6
42
```

The second argument is the file name reports and `err.stack` use. An engine isn’t thread-safe: use one per thread.

## Pass values and functions in

Values go onto the global object through the engine’s realm. Put that setup in `ScriptEngineOptions.Configure`: when a script fails, the engine replays the run to explain it, and the replay calls `Configure` again to rebuild the same world.

```csharp
var shop = new ScriptEngine(new ScriptEngineOptions
{
    Configure = e =>
    {
        JsRealm realm = e.MainRealm;
        JsObject order = realm.NewObject();
        realm.DefineData(order, "id", JsValue.FromNumber(1042));
        realm.DefineData(order, "items", JsValue.FromObject(realm.NewArray([JsValue.FromNumber(4), JsValue.FromNumber(6)])));
        realm.DefineData(realm.Global, "order", JsValue.FromObject(order));
        realm.DefineData(realm.Global, "tax", JsValue.FromObject(
            realm.NewFunction("tax", 1, (_, args) => JsValue.FromNumber(Math.Round(args[0].AsNumber * 0.08, 2)))), JsAttributes.Method);
    },
});
JsValue total = shop.Evaluate("const subtotal = order.items.reduce((a, b) => a + b, 0);\nsubtotal + tax(subtotal)", "total.js");
Console.WriteLine($"total: {total.AsNumber}");
```

```text
total: 10.8
```

A host function is `(this, args) => JsValue`. For whole .NET classes, records and enums, let the generator write the bindings: see [Expose .NET objects with [JsExport]](https://recursiveai.org/oneecma/cookbook/jsexport/). For tasks, exceptions in both directions and reading script objects back, see [Call C# from JavaScript](https://recursiveai.org/oneecma/cookbook/host-functions/).

## Read the report when a script fails

A script error reaches C# as a `JsException`. `Message` is what the script would see as `` `${err.name}: ${err.message}` ``, and `Report` is the structured diagnostic; `DiagnosticRenderer.Render` prints it:

```csharp
try
{
    shop.Evaluate("const count = order.item.length;", "count.js");
}
catch (JsException e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(DiagnosticRenderer.Render(e.Report!));
    Console.WriteLine(shop.ToDisplayString(shop.MainRealm.Get(e.Value.AsObject!, "stack")));
}
```

```text
TypeError: Cannot read properties of undefined (reading 'length')
error[TypeError]: Cannot read properties of undefined (reading 'length')
 --> count.js:1:21
  |
1 | const count = order.item.length;
  |                     ^^^^ `order.item` is undefined
  |
  = help: did you mean `order.items`? (Array, 2 items)
  = stack: <top-level> (count.js:1)

TypeError: Cannot read properties of undefined (reading 'length')
    at count.js:1:26
```

The headline and `err.stack` are V8’s: Node prints the same two lines for this script. The underline, the label and the suggestion come from a replay of the failed run with tracing on, so a run that succeeds pays nothing for them. The suggestion only ever names something the object actually holds.

The same report comes as a compact rendering for a model’s context and as versioned JSON: see [Error reports: text, compact and JSON](https://recursiveai.org/oneecma/cookbook/error-reports/).

## Run untrusted code under limits

`EngineLimits` bounds a run. When a limit is hit the run ends with a `JsLimitException`, which script can’t catch, and its report says which limit and where:

```csharp
var sandbox = new ScriptEngine(new ScriptEngineOptions
{
    Limits = new EngineLimits { Fuel = 1_000_000, WallClockMs = 2_000, MaxCallDepth = 2_000 },
});
try
{
    sandbox.Evaluate("let n = 0;\nwhile (true) { n++; }", "spin.js");
}
catch (JsLimitException e)
{
    Console.WriteLine($"{e.Kind}: {e.Message}");
    Console.WriteLine(DiagnosticRenderer.Render(e.Report!));
}
```

```text
Fuel: Execution exceeded the step limit of 1000000 steps
error[Limit]: Execution exceeded the step limit of 1000000 steps
 --> spin.js:2:1
  |
2 | while (true) { n++; }
  | ^^^^^^^^^^^^^^^^^^^^^
  |
  = stack: <top-level> (spin.js:2)
```

Fuel is charged per call, per loop back-edge, per step of RegExp backtracking and per unit of work in long-running built-ins. The defaults are generous (4 billion steps, 2 GiB of allocation, no wall-clock limit), so set them for anything you don’t trust. Every limit, cancellation included, is in [Run untrusted JavaScript with limits](https://recursiveai.org/oneecma/cookbook/sandbox/).

## Check source without running it

`ScriptEngine.Check` parses and runs the early errors only, and returns every error, not just the first:

```csharp
foreach (Diagnostic d in ScriptEngine.Check("let a = 1;\nlet a = 2;\nlet b = ;", new ParseOptions { FileName = "two.js" }))
{
    Console.WriteLine($"{d.Position.Line}:{d.Position.Column} {d.Code}: {d.Headline}");
}
```

```text
2:5 redeclaration: Identifier 'a' has already been declared
3:9 unexpected-token: Unexpected token ';'
```

`Code` is a stable identifier that is never reworded. `ScriptEngine.CheckAndRepair` also applies the syntax-only repairs that have a single plausible fix: see [Run code a model wrote](https://recursiveai.org/oneecma/cookbook/run-model-code/).

## Next

- [Run untrusted JavaScript with limits](https://recursiveai.org/oneecma/cookbook/sandbox/)
- [Expose .NET objects with [JsExport]](https://recursiveai.org/oneecma/cookbook/jsexport/)
- [Error reports: text, compact and JSON](https://recursiveai.org/oneecma/cookbook/error-reports/)
- [The host API](https://recursiveai.org/oneecma/docs/host-api/), the reference for everything above
- [What is supported](https://recursiveai.org/oneecma/docs/overview/#what-is-supported) and [how it is checked](https://recursiveai.org/oneecma/docs/overview/#conformance)
