quickjs

MoonBit bindings for the QuickJS JavaScript engine.

quickjs
javascript
ffi
native
moon add justjavac/quickjs@0.1.5
Download zip
Author
Version
0.1.5
License
MIT
Last updated
24 days ago
Downloads
87

Dependencies

README

#quickjs

MoonBit bindings for the QuickJS JavaScript engine. This package exposes three public handles:

  • Runtime for allocator state and pending jobs
  • Context for execution scope and exception state
  • Value for JavaScript values

Destroy values before contexts, and destroy contexts before the runtime.

#Quick Start

test {
let runtime = Runtime::new()
let context = runtime.new_context()

let result = context.eval("1 + 2")
guard !result.is_exception() else {
let message = context.exception_message()
result.destroy()
fail(message)
}

inspect(result.to_int32(context), content="3")

result.destroy()
context.destroy()
runtime.destroy()
}

#Features

  • Native-only MoonBit package built on top of a small FFI surface
  • Vendored QuickJS libraries for Windows, Linux, and macOS
  • Explicit lifecycle management for runtimes, contexts, and values
  • Routines for evaluation, JSON parsing, property access, and Promise jobs

#
Context

#external
pub type Context

Represents a QuickJS execution context created from a runtime.

Contexts hold JavaScript globals, evaluation state, and exception state. Multiple contexts can share a runtime while keeping their own global object. Destroy the context after every value created from it has been destroyed.

Example

test {
let runtime = Runtime::new()
let context = Context::new(runtime)
let value = context.eval("'moon' + 'bit'")
inspect(value.to_string_lossy(context), content="moonbit")
value.destroy()
context.destroy()
runtime.destroy()
}

#
Context::destroy

fn Context::destroy(self : Context) -> Unit

Destroys the context and releases its native resources.

Destroy every Value created from this context before destroying the context itself.

#
Context::eval

fn Context::eval(self : Context, code : String, filename? : String, flags? : Int) -> Value

Evaluates JavaScript source code and returns the resulting value.

By default the code is evaluated as a global script with the synthetic file name <eval>. Override filename for better diagnostics and flags when you need module parsing, compile-only mode, or strict execution. The returned handle is always owned by the caller. If evaluation fails, the result is the QuickJS exception sentinel, and the detailed error can be consumed with Context::get_exception() or Context::exception_message().

Example

test {
let runtime = Runtime::new()
let context = runtime.new_context()
let value = context.eval("40 + 2")
inspect(value.to_int32(context), content="42")
value.destroy()
context.destroy()
runtime.destroy()
}

Error Handling

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let failed = context.eval("throw new Error('boom')")
inspect(failed.is_exception(), content="true")
failed.destroy()
inspect(context.exception_message(), content="Error: boom")
}

#
Context::exception_message

fn Context::exception_message(self : Context) -> String

Retrieves the current pending exception as a human-readable string.

Like Context::get_exception(), this consumes the pending exception from the context. This is convenient for logging, testing, and simple host-side error reporting when you do not need structured access to the exception object.

#
Context::get_exception

fn Context::get_exception(self : Context) -> Value

Retrieves and clears the current pending exception object.

QuickJS stores only one pending exception per context, so calling this method consumes that exception state. This is the low-level path when you need to inspect exception properties instead of only showing its message.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let failed = context.eval("throw new Error('boom')")
inspect(failed.is_exception(), content="true")
failed.destroy()

let exception = context.get_exception()
defer exception.destroy()
inspect(exception.to_string_lossy(context), content="Error: boom")
}

#
Context::global_object

fn Context::global_object(self : Context) -> Value

Returns the context global object.

Use this when you want to install host data or functions onto the global scope before evaluating more code. The caller owns the returned handle and should destroy it after use.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let global = context.global_object()
defer global.destroy()
let answer = context.new_int32(42)
defer answer.destroy()

ignore(global.set_property(context, "answer", answer))

let result = context.eval("answer + 1")
defer result.destroy()
inspect(result.to_int32(context), content="43")
}

#
Context::json_stringify

fn Context::json_stringify(self : Context, value : Value) -> Value

Serializes a JavaScript value using JSON.stringify.

The result is itself a JavaScript string value, which you can convert with Value::to_string_lossy(). Stringification errors, such as cyclic structures or throwing accessors, surface through the normal exception sentinel.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let array = context.new_array()
defer array.destroy()
let first = context.new_string("moon")
defer first.destroy()
let second = context.new_int32(2)
defer second.destroy()

ignore(array.set_index(context, 0, first))
ignore(array.set_index(context, 1, second))

let json = context.json_stringify(array)
defer json.destroy()
inspect(json.to_string_lossy(context), content="[\"moon\",2]")
}

#
Context::new

fn Context::new(runtime : Runtime) -> Context

Creates a new QuickJS context from the given runtime.

Most code should prefer Runtime::new_context(), which reads more naturally at the call site while producing the same result.

#
Context::new_array

fn Context::new_array(self : Context) -> Value

Creates a new JavaScript array.

The returned value behaves like [] in JavaScript and can be populated with Value::set_index() from host code.

#
Context::new_bool

fn Context::new_bool(self : Context, value : Bool) -> Value

Creates a JavaScript boolean value.

This returns true or false as a QuickJS handle that can be stored in objects, arrays, or function arguments.

#
Context::new_float64

fn Context::new_float64(self : Context, value : Double) -> Value

Creates a JavaScript floating-point value.

Use this when the result should preserve fractional data or when JavaScript code expects a non-integer numeric value.

#
Context::new_int32

fn Context::new_int32(self : Context, value : Int) -> Value

Creates a JavaScript 32-bit integer value.

This is useful when preparing arguments or object properties from MoonBit without going through JavaScript source text.

#
Context::new_object

fn Context::new_object(self : Context) -> Value

Creates a new plain JavaScript object.

The returned value behaves like {} in JavaScript and is a convenient starting point for Value::set_property() calls from MoonBit.

#
Context::new_string

fn Context::new_string(self : Context, text : String) -> Value

Creates a JavaScript string from MoonBit text.

The returned handle owns a QuickJS string value and must be destroyed by the caller.

#
Context::null

fn Context::null(self : Context) -> Value

Returns a wrapped JavaScript null value.

This is convenient when populating objects and arrays from MoonBit and you want to represent an explicit JavaScript null rather than an omitted field.

#
Context::parse_json

fn Context::parse_json(self : Context, text : String, filename? : String) -> Value

Parses JSON text into a JavaScript value.

The returned value can be inspected with the usual property and conversion helpers on Value. Parse failures follow the normal QuickJS exception path, so check Value::is_exception() when decoding untrusted input.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let parsed = context.parse_json("{\"name\":\"moonbit\",\"items\":[1,2,3]}")
defer parsed.destroy()

let name = parsed.get_property(context, "name")
defer name.destroy()
inspect(name.to_string_lossy(context), content="moonbit")

let items = parsed.get_property(context, "items")
defer items.destroy()
let third = items.get_index(context, 2)
defer third.destroy()
inspect(third.to_int32(context), content="3")
}

#
Context::to_bool

fn Context::to_bool(self : Context, value : Value) -> Bool

Converts a JavaScript value to a boolean.

This applies JavaScript truthiness rules through QuickJS, so numbers, strings, objects, null, and undefined behave the same way they would in script code.

#
Context::to_float64

fn Context::to_float64(self : Context, value : Value) -> Double

Converts a JavaScript value to a floating-point number.

This applies QuickJS numeric coercion rules before returning the MoonBit Double. It is the direct context-based equivalent of Value::to_float64(context).

#
Context::to_int32

fn Context::to_int32(self : Context, value : Value) -> Int

Converts a JavaScript value to a 32-bit integer.

This applies QuickJS numeric coercion rules before returning the MoonBit Int. Use this when host code wants the converted number immediately without calling the equivalent helper on Value.

#
Context::to_string_lossy

fn Context::to_string_lossy(self : Context, value : Value) -> String

Converts a JavaScript value to a string using QuickJS coercion.

This is a lossy host-facing conversion that always returns a MoonBit String. It is especially useful for diagnostics, test assertions, and user-facing logging of JavaScript results.

#
Context::undefined

fn Context::undefined(self : Context) -> Value

Returns a wrapped JavaScript undefined value.

This is convenient when modeling omitted JavaScript values explicitly while still passing a concrete handle through the host API.

#
Runtime

#external
pub type Runtime

Owns the underlying QuickJS runtime and scheduler state.

A Runtime is the root handle for every other object in this package. Create one runtime first, derive one or more contexts from it, and destroy the runtime after every context and value has already been released.

Lifecycle

  • Create the runtime with Runtime::new()
  • Create contexts with Runtime::new_context()
  • Destroy all Value and Context handles before Runtime::destroy()

Example

test {
let runtime = Runtime::new()
let context = runtime.new_context()
let result = context.eval("1 + 2")
inspect(result.to_int32(context), content="3")
result.destroy()
context.destroy()
runtime.destroy()
}

#
Runtime::destroy

fn Runtime::destroy(self : Runtime) -> Unit

Destroys the runtime and releases its native resources.

Destroy every context and value created from the runtime before calling this method. After destruction the runtime handle must not be used again.

#
Runtime::execute_pending_job

fn Runtime::execute_pending_job(self : Runtime) -> Int

Executes one pending job from the QuickJS job queue.

Hosts that manually drive Promise resolution typically call this in a loop while Runtime::is_job_pending() remains true. The returned integer is the raw QuickJS status code for that job.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let setup = context.eval(
"globalThis.pending = 0; Promise.resolve(41).then(v => { globalThis.pending = v + 1; });",
)
defer setup.destroy()

while runtime.is_job_pending() {
ignore(runtime.execute_pending_job())
}

let result = context.eval("pending")
defer result.destroy()
inspect(result.to_int32(context), content="42")
}

#
Runtime::is_job_pending

fn Runtime::is_job_pending(self : Runtime) -> Bool

Reports whether the runtime still has pending Promise jobs.

Use this together with Runtime::execute_pending_job() when the host needs to drive the QuickJS job queue manually.

#
Runtime::new

fn Runtime::new() -> Runtime

Creates a fresh QuickJS runtime.

The runtime owns allocator state, garbage collection, and the pending job queue shared by contexts created from it. Create one runtime first, then derive the contexts that should share its memory limits and job queue.

#
Runtime::new_context

fn Runtime::new_context(self : Runtime) -> Context

Creates a new execution context owned by this runtime.

This is the usual entry point for evaluating scripts and constructing JavaScript values. Each context gets its own globals while still sharing the runtime scheduler and memory configuration.

#
Runtime::run_gc

fn Runtime::run_gc(self : Runtime) -> Unit

Runs a garbage-collection cycle immediately.

This is useful after releasing many values or before measuring retained memory in tests and embedding diagnostics.

#
Runtime::set_can_block

fn Runtime::set_can_block(self : Runtime, can_block : Bool) -> Unit

Allows or forbids blocking operations inside QuickJS.

Embedders that need strict non-blocking behavior can disable blocking at runtime with this switch.

#
Runtime::set_gc_threshold

fn Runtime::set_gc_threshold(self : Runtime, threshold : UInt64) -> Unit

Sets the garbage-collection threshold in bytes.

Lower values trigger more frequent collections, while higher values favor throughput over prompt reclamation.

#
Runtime::set_info

fn Runtime::set_info(self : Runtime, info : String) -> Unit

Stores an informational label on the runtime.

QuickJS uses this string for diagnostics and embedding metadata.

#
Runtime::set_max_stack_size

fn Runtime::set_max_stack_size(self : Runtime, stack_size : UInt64) -> Unit

Sets the maximum native stack size observed by QuickJS.

This can help bound recursion depth in embedded use cases.

#
Runtime::set_memory_limit

fn Runtime::set_memory_limit(self : Runtime, limit : UInt64) -> Unit

Sets the maximum heap size that QuickJS may use for this runtime.

This limit is expressed in bytes and is best configured before executing untrusted or memory-intensive scripts.

#
Runtime::update_stack_top

fn Runtime::update_stack_top(self : Runtime) -> Unit

Refreshes the current native stack top used by QuickJS stack checks.

Call this if the embedding environment changes the effective stack frame origin before running more JavaScript work.

#
Value

#external
pub type Value

Wraps a JavaScript value produced by QuickJS.

A Value may represent primitives, objects, arrays, exceptions, or special values such as null and undefined. Values use explicit lifetime management, so call Value::destroy() once the handle is no longer needed.

Example

test {
let runtime = Runtime::new()
let context = runtime.new_context()
let value = context.new_bool(true)
inspect(value.to_bool(context), content="true")
value.destroy()
context.destroy()
runtime.destroy()
}

#
Value::destroy

fn Value::destroy(self : Value) -> Unit

Destroys the value and releases its native QuickJS handle.

Call this once the value is no longer needed. A convenient pattern is to create the value and immediately register defer value.destroy() in the surrounding scope.

#
Value::dup

fn Value::dup(self : Value) -> Value

Creates another handle that refers to the same underlying JavaScript value.

This is useful when the same value needs to outlive another owner or be stored in multiple places. The duplicated handle must be destroyed independently from the original handle.

#
Value::get_index

fn Value::get_index(self : Value, context : Context, index : UInt) -> Value

Reads an indexed property from an array-like JavaScript value.

The returned property is a new handle owned by the caller. This is useful for traversing arrays, tuples, and array-like objects exposed from JavaScript.

#
Value::get_property

fn Value::get_property(self : Value, context : Context, name : String) -> Value

Reads a named property from an object-like JavaScript value.

The returned property is a new handle owned by the caller. If the property access throws, for example because of a getter or proxy trap, the result is the QuickJS exception sentinel.

#
Value::is_bool

fn Value::is_bool(self : Value) -> Bool

Reports whether this value is a JavaScript boolean.

Use this before coercion when host code wants to preserve whether the original JavaScript value was truly true or false.

#
Value::is_exception

fn Value::is_exception(self : Value) -> Bool

Reports whether this value is the QuickJS exception sentinel.

Values returned from failed Context::eval() or Context::parse_json() calls can be checked with this method before performing conversions.

#
Value::is_null

fn Value::is_null(self : Value) -> Bool

Reports whether this value is JavaScript null.

This only matches the actual null value; missing properties and omitted results often surface as undefined instead.

#
Value::is_number

fn Value::is_number(self : Value) -> Bool

Reports whether this value is a JavaScript number.

QuickJS numeric values include both integer and floating-point representations, so this is the broad predicate to test before numeric conversion.

#
Value::is_object

fn Value::is_object(self : Value) -> Bool

Reports whether this value is a JavaScript object.

Arrays also count as objects under QuickJS semantics.

#
Value::is_string

fn Value::is_string(self : Value) -> Bool

Reports whether this value is a JavaScript string.

This checks the underlying JavaScript type directly instead of relying on host-side string coercion.

#
Value::is_undefined

fn Value::is_undefined(self : Value) -> Bool

Reports whether this value is JavaScript undefined.

This is useful when distinguishing omitted JavaScript results from explicit null payloads returned by application code.

#
Value::set_index

fn Value::set_index(self : Value, context : Context, index : UInt, property : Value) -> Int

Writes an indexed property onto an array-like JavaScript value.

The return value is the raw QuickJS status code, where a negative value indicates failure. This pairs naturally with Context::new_array() when building JavaScript arrays from MoonBit. As with Value::set_property(), the passed property handle remains owned by the caller.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let array = context.new_array()
defer array.destroy()
let first = context.new_string("moon")
defer first.destroy()

ignore(array.set_index(context, 0, first))

let read_back = array.get_index(context, 0)
defer read_back.destroy()
inspect(read_back.to_string_lossy(context), content="moon")
}

#
Value::set_property

fn Value::set_property(self : Value, context : Context, name : String, property : Value) -> Int

Writes a named property onto an object-like JavaScript value.

The return value is the raw QuickJS status code, where a negative value indicates failure. This is the usual host-side way to populate objects returned by Context::new_object(). The property handle remains owned by the caller, so you should still destroy it after the write.

Example

test {
let runtime = Runtime::new()
defer runtime.destroy()
let context = runtime.new_context()
defer context.destroy()

let object = context.new_object()
defer object.destroy()
let answer = context.new_int32(42)
defer answer.destroy()

ignore(object.set_property(context, "answer", answer))

let read_back = object.get_property(context, "answer")
defer read_back.destroy()
inspect(read_back.to_int32(context), content="42")
}

#
Value::to_bool

fn Value::to_bool(self : Value, context : Context) -> Bool

Converts this value to a boolean with the given context.

The result follows normal JavaScript truthiness semantics through the owning context.

#
Value::to_float64

fn Value::to_float64(self : Value, context : Context) -> Double

Converts this value to a floating-point number with the given context.

This is convenient when chaining property access and numeric conversion from the value handle itself.

#
Value::to_int32

fn Value::to_int32(self : Value, context : Context) -> Int

Converts this value to a 32-bit integer with the given context.

This delegates to Context::to_int32() and therefore follows the same QuickJS coercion rules used during script execution.

#
Value::to_string_lossy

fn Value::to_string_lossy(self : Value, context : Context) -> String

Converts this value to a string with the given context.

Use this for host-facing logging and assertions when you want JavaScript string coercion applied automatically.

#
eval_flag_async

let eval_flag_async : Int

Enables async function and top-level async related evaluation behavior.

Use this when the evaluated source should participate in QuickJS async execution semantics, such as scheduling Promise jobs that the host will later drive with Runtime::execute_pending_job().

#
eval_flag_backtrace_barrier

let eval_flag_backtrace_barrier : Int

Prevents the current stack trace from crossing this evaluation boundary.

This can be useful when embedding QuickJS and controlling how errors are surfaced back to host code, especially when you want the embedding layer to hide internal helper frames.

#
eval_flag_compile_only

let eval_flag_compile_only : Int

Compiles the source without executing it immediately.

This is typically combined with one of the evaluation type constants when the host wants syntax validation or precompilation without running the script body immediately.

#
eval_flag_strict

let eval_flag_strict : Int

Requests strict-mode parsing during evaluation.

Combine this flag with an evaluation type such as eval_type_global. For example, eval_type_global | eval_flag_strict evaluates a top-level script in strict mode.

#
eval_type_direct

let eval_type_direct : Int

Marks the source as a direct eval(...) call.

This mirrors QuickJS evaluation semantics for code that should behave like a direct JavaScript eval.

#
eval_type_global

let eval_type_global : Int

Evaluates code as a global script.

Use this as the default mode for ordinary snippets that should run against the current context global object. Evaluation types occupy the low bits of the QuickJS flag word and can be combined with eval_flag_* constants.

#
eval_type_indirect

let eval_type_indirect : Int

Marks the source as an indirect eval(...) call.

This is useful when you need QuickJS to apply indirect-eval scoping rules.

#
eval_type_module

let eval_type_module : Int

Evaluates code as an ES module.

Combine this with module source text when you want import or export syntax to be parsed by QuickJS.

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io