# How to Use Monty from JavaScript/TypeScript with napi-rs

> Integrate Monty the sandboxed Python interpreter into your JavaScript/TypeScript projects with napi-rs. Install the @pydantic/monty package for seamless Rust-based Python execution.

- Repository: [Pydantic/monty](https://github.com/pydantic/monty)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You can use Monty from JavaScript/TypeScript by installing the `@pydantic/monty` npm package, which exposes a Rust-based sandboxed Python interpreter through napi-rs bindings that support synchronous, iterative, and asynchronous execution patterns.**

The `@pydantic/monty` package provides a high-performance bridge between Node.js and the Monty sandboxed Python interpreter. When you use Monty from JavaScript/TypeScript with napi-rs, you get type-safe access to Python execution with automatic data conversion, resource limits, and support for external JavaScript functions.

## What Is the Monty JavaScript/TypeScript Bridge?

The napi-rs layer in `@pydantic/monty` wraps the Rust-implemented Monty interpreter and exposes it to Node.js through a thin, efficient binding layer. This architecture allows you to execute untrusted Python code safely while maintaining native JavaScript ergonomics.

| Component | Role | Source |
|---|---|---|
| **`Monty` class** | Parses Python code once, then runs it (or runs it iteratively) with optional inputs, resource limits, and external callbacks. | [`crates/monty-js/src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/monty_cls.rs) |
| **`MontySnapshot` / `MontyComplete`** | Represent a paused external-function call (`MontySnapshot`) and a finished run (`MontyComplete`). | [`crates/monty-js/src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/monty_cls.rs) |
| **Conversion layer** | Bidirectional conversion between Rust `MontyObject` and JavaScript values (including `Map`, `Set`, `Buffer`, `BigInt`, etc.). | [`crates/monty-js/src/convert.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/convert.rs) |
| **`runMontyAsync` helper** | Executes code that may perform *await*-style external functions. | [`crates/monty-js/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/lib.rs) |
| **Error types** | `MontySyntaxError`, `MontyRuntimeError`, `MontyTypingError` map Python-side errors to JavaScript exceptions. | [`crates/monty-js/src/exceptions.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/exceptions.rs) |
| **Resource-limit struct** | `JsResourceLimits` lets the host cap allocations, runtime, memory, and recursion depth. | [`crates/monty-js/src/limits.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/limits.rs) |

## Installation and Basic Setup

Install the package via npm or your preferred package manager:

```bash
npm install @pydantic/monty

```

Import the main classes and helpers in your TypeScript or JavaScript file:

```typescript
import { Monty, runMontyAsync, JsResourceLimits } from '@pydantic/monty'

```

## Core API Components

### The Monty Class

The `Monty` class is the primary entry point defined in [`crates/monty-js/src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/monty_cls.rs). When you instantiate `new Monty(code, options?)`, the constructor invokes `Monty::create` in Rust, which parses the Python source and optionally runs static type checking.

Key methods include:

- **`run(options?)`**: Executes the parsed code synchronously.
- **`start()`**: Begins iterative execution, returning a `MontySnapshot` when the code hits an external function.
- **`dump()`**: Serializes the compiled Monty instance to a `Buffer` for caching.
- **`load(buffer)`**: Static method to restore a previously dumped instance.

### Execution Results: MontySnapshot and MontyComplete

When using iterative execution via `start()` and `resume()`, the runtime returns specific objects:

- **`MontySnapshot`**: Represents a paused execution state when Python calls an external JavaScript function. It stores the function name, positional/keyword arguments (as `MontyObject` values), and an optional print-callback reference. Call `snapshot.resume({returnValue, exception})` to continue execution.
- **`MontyComplete`**: Represents a finished execution. Access the final result via the `.output` getter.

### Error Handling

The napi-rs bridge maps Python-side errors to specific JavaScript exception classes defined in [`crates/monty-js/src/exceptions.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/exceptions.rs):

- **`MontySyntaxError`**: Raised when Python code fails to parse.
- **`MontyRuntimeError`**: Raised for runtime exceptions (e.g., NameError, TypeError) or when resource limits are exceeded.
- **`MontyTypingError`**: Raised when static type checking is enabled and fails.

All errors wrap the internal `JsMontyException` struct to preserve stack traces and error messages across the FFI boundary.

### Resource Limits

The `JsResourceLimits` interface (defined in [`crates/monty-js/src/limits.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/limits.rs)) allows you to sandbox Python execution by capping:

- **`maxAllocations`**: Maximum number of object allocations.
- **`maxDurationSecs`**: Maximum execution time in seconds.
- **`maxMemory`**: Maximum memory usage in bytes.
- **`maxRecursionDepth`**: Maximum Python call stack depth.

Pass these limits to the `run()` or `start()` methods via the `limits` option.

## Execution Patterns

### Synchronous Execution with run()

For simple scripts without external functions or async operations, use the synchronous `run()` method. This path invokes `MontyRun::run` directly in Rust after converting inputs via `Monty::extract_input_values`.

```typescript
import { Monty } from '@pydantic/monty'

const m = new Monty('1 + 2')
const result = m.run()  // → 3

```

### Iterative Execution with start() and resume()

When your Python code calls external JavaScript functions, use iterative execution to handle the calls manually. The `start()` method begins execution and returns a `MontySnapshot` whenever Python invokes an external function.

```typescript
import { Monty, MontySnapshot, MontyComplete } from '@pydantic/monty'

const m = new Monty('add(a, b)', { externalFunctions: ['add'] })
let prog = m.start()

while (prog instanceof MontySnapshot) {
  console.log(`Calling ${prog.functionName}`)
  prog = prog.resume({ returnValue: 10 })
}

if (prog instanceof MontyComplete) {
  console.log('Result:', prog.output)
}

```

### Asynchronous Execution with runMontyAsync()

Because `run()` cannot suspend for async work, the package exports `runMontyAsync(montyInstance, runOptions)` from [`crates/monty-js/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/lib.rs). This helper internally uses `Monty::start` and repeatedly awaits Promises that resolve when snapshots are ready, enabling idiomatic `await` patterns from JavaScript.

```typescript
import { Monty, runMontyAsync } from '@pydantic/monty'

const code = `
async def fetch_data(url):
    return await external_fetch(url)

result = await fetch_data("https://example.com")
`
const m = new Monty(code, {
  externalFunctions: ['external_fetch']
})

const out = await runMontyAsync(m, {
  externalFunctions: {
    external_fetch: async (url: string) => {
      const resp = await fetch(url)
      return resp.text()
    }
  }
})

```

## Data Type Conversion Between JavaScript and Python

The conversion layer in [`crates/monty-js/src/convert.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/convert.rs) handles bidirectional translation between Rust `MontyObject` and JavaScript values. This ensures seamless data passing without manual serialization.

Supported conversions include:

- **Objects and Primitives**: JavaScript objects and primitives map directly to Python dicts and scalars.
- **Map and Set**: Native JavaScript `Map` and `Set` instances convert to Python `dict` and `set` equivalents.
- **Buffer**: Node.js `Buffer` objects translate to Python `bytes`.
- **BigInt**: JavaScript `BigInt` values map to Python integers without precision loss.

When Python code returns values, the `monty_to_js` function in the conversion layer transforms Rust `MontyObject` back to appropriate JavaScript types.

## Advanced Usage Examples

### Providing Input Variables

Pass variables to Python code by declaring them in the constructor and supplying values at runtime:

```typescript
import { Monty } from '@pydantic/monty'

const m = new Monty('x * y', { inputs: ['x', 'y'] })
const result = m.run({ inputs: { x: 7, y: 6 } }) // → 42

```

The `Monty::extract_input_values` method in [`crates/monty-js/src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/monty_cls.rs) handles the conversion from JavaScript objects to Python scope variables.

### External Functions (Synchronous)

Register JavaScript functions that Python code can call during execution:

```typescript
import { Monty } from '@pydantic/monty'

const m = new Monty('add(a, b)', { externalFunctions: ['add'] })
const sum = m.run({
  externalFunctions: {
    add: (a: number, b: number) => a + b,
  },
})

```

When external functions are declared, the interpreter uses `run_with_external_functions` instead of the direct `MontyRun::run` path.

### Asynchronous External Functions

For I/O-bound operations, use `runMontyAsync` to handle Promise-based external functions:

```typescript
import { Monty, runMontyAsync } from '@pydantic/monty'

const code = `
async def fetch_data(url):
    return await external_fetch(url)

result = await fetch_data("https://example.com")
`
const m = new Monty(code, {
  inputs: ['url'],
  externalFunctions: ['external_fetch'],
})

const out = await runMontyAsync(m, {
  inputs: { url: 'https://example.com' },
  externalFunctions: {
    external_fetch: async (url: string) => {
      const resp = await fetch(url)
      return resp.text()
    },
  },
})

```

The `runMontyAsync` helper in [`crates/monty-js/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/lib.rs) internally uses `Monty::start` and repeatedly awaits Promises that resolve when snapshots are ready.

### Manual Snapshot Handling

For fine-grained control over external function calls, manually manage the execution loop:

```typescript
import { Monty, MontySnapshot, MontyComplete } from '@pydantic/monty'

const m = new Monty('a() + b()', {
  externalFunctions: ['a', 'b'],
})

let prog = m.start()

while (prog instanceof MontySnapshot) {
  console.log('Calling', prog.functionName, 'with args', await prog.args())
  prog = prog.resume({ returnValue: 10 })
}

if (prog instanceof MontyComplete) {
  console.log('Final result:', await prog.output) // → 20
}

```

The `MontySnapshot` stores the function name, positional/keyword arguments as `MontyObject` values, and an optional print-callback reference. Calling `resume` with a `returnValue` or `exception` continues execution.

### Serialization and Caching

Avoid reparsing overhead by serializing compiled Monty instances:

```typescript
import { Monty } from '@pydantic/monty'

const m = new Monty('heavy_computation()')
const data = m.dump() // Returns Buffer

// Later – restore without reparsing
const restored = Monty.load(data)
const out = restored.run()

```

Snapshots can also be serialized using `snapshot.dump()` and later restored via `MontySnapshot.load(buf)`, enabling suspension and resumption of execution across process boundaries.

### Enforcing Resource Limits

Prevent runaway execution by setting resource constraints:

```typescript
import { Monty, JsResourceLimits } from '@pydantic/monty'

const limits: JsResourceLimits = {
  maxAllocations: 10_000,
  maxDurationSecs: 2,
  maxMemory: 2 * 1024 * 1024, // 2 MiB
  maxRecursionDepth: 200,
}

const m = new Monty('while True: pass')
try {
  m.run({ limits })
} catch (e) {
  console.error('Execution stopped:', e.message)
}

```

The `JsResourceLimits` struct in [`crates/monty-js/src/limits.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/limits.rs) converts these constraints into the internal Rust resource tracker, triggering a `MontyRuntimeError` when limits are exceeded.

## Key Source Files in the Repository

Understanding the source structure helps when debugging or extending the bindings:

| Path | Purpose |
|---|---|
| [`crates/monty-js/README.md`](https://github.com/pydantic/monty/blob/main/crates/monty-js/README.md) | High-level usage guide and npm-install instructions. |
| [`crates/monty-js/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/lib.rs) | Exposes the public N-API symbols (`Monty`, `runMontyAsync`, error classes, etc.). |
| [`crates/monty-js/src/monty_cls.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/monty_cls.rs) | Core implementation of the JavaScript-side `Monty` class, `MontySnapshot`, `MontyComplete`, and all option structs. |
| [`crates/monty-js/src/convert.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/convert.rs) | Handles conversion between `MontyObject` and `napi` values (Maps ↔ `Map`, Sets ↔ `Set`, Bytes ↔ `Buffer`, BigInt handling, marker objects). |
| [`crates/monty-js/src/exceptions.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/exceptions.rs) | Definitions of `JsMontyException` and the three typed error classes that are re-exported. |
| [`crates/monty-js/src/limits.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/limits.rs) | Definition of `JsResourceLimits` and conversion to the internal Rust tracker. |
| [`crates/monty-js/package.json`](https://github.com/pydantic/monty/blob/main/crates/monty-js/package.json) | NPM package metadata (entry point, TypeScript typings). |
| [`crates/monty-js/index.d.ts`](https://github.com/pydantic/monty/blob/main/crates/monty-js/index.d.ts) | TypeScript declaration file that describes the public API for IDEs and `tsc`. |

These files together form the **napi-rs bridge** that lets you write ordinary JavaScript/TypeScript code while safely executing untrusted Python inside the Rust sandbox.

## Summary

- **Install** the bridge via `npm install @pydantic/monty` to use Monty from JavaScript/TypeScript with napi-rs.
- **Instantiate** the `Monty` class with Python code and optional metadata (inputs, external functions, resource limits).
- **Execute** using `run()` for synchronous code, `runMontyAsync()` for async external functions, or manual `start()`/`resume()` for fine-grained control over external calls.
- **Convert** data automatically between JavaScript and Python via the layer in [`crates/monty-js/src/convert.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/convert.rs), supporting `Map`, `Set`, `Buffer`, and `BigInt`.
- **Serialize** compiled instances with `dump()` and `load()` to avoid reparsing overhead, or serialize snapshots to pause and resume execution later.
- **Enforce** sandbox constraints using `JsResourceLimits` to cap memory, CPU time, allocations, and recursion depth.

## Frequently Asked Questions

### How do I install the Monty npm package?

Install the package using your preferred package manager. The package includes prebuilt binaries for common platforms, so you typically do not need a Rust toolchain:

```bash
npm install @pydantic/monty

```

Once installed, import the `Monty` class and any helper functions you need from the package.

### Can I use async/await with Monty external functions?

Yes, but you must use the `runMontyAsync` helper instead of the standard `run()` method. The `run()` method cannot suspend for asynchronous work, so `runMontyAsync` (exported from [`crates/monty-js/src/lib.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/lib.rs)) manages the event loop for you. It internally calls `Monty::start`, awaits the Promise returned by your async external function, and resumes execution with the resolved value.

### What happens when Python code hits a resource limit?

When execution exceeds any limit defined in `JsResourceLimits` (such as `maxMemory`, `maxDurationSecs`, or `maxAllocations`), the interpreter stops and throws a `MontyRuntimeError`. This error class (defined in [`crates/monty-js/src/exceptions.rs`](https://github.com/pydantic/monty/blob/main/crates/monty-js/src/exceptions.rs)) wraps the internal Rust error and includes details about which limit was breached, allowing you to catch and handle sandbox violations gracefully.

### How do I serialize and restore a Monty instance?

To avoid the overhead of reparsing Python source code, use the `dump()` instance method to serialize a compiled `Monty` instance into a `Buffer`. Later, use the static `Monty.load(buffer)` method to restore the instance without re-parsing. This is particularly useful for caching compiled code in serverless environments or saving state between process restarts. You can also serialize individual snapshots using `snapshot.dump()` and restore them with `MontySnapshot.load(buf)` to pause and resume execution across different sessions.