npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

lua-state

v1.1.3

Published

Run real Lua (5.1-5.4 & LuaJIT) inside Node.js - native N-API bindings with prebuilt binaries and full TypeScript support.

Readme

lua-state - Native Lua & LuaJIT bindings for Node.js

Embed real Lua (5.1-5.4) and LuaJIT in Node.js with native N-API bindings. Create Lua VMs, execute code, share values between languages - no compiler required with prebuilt binaries.

npm Node License: MIT

⚙️ Features

  • Multiple Lua versions - Supports Lua 5.1–5.4 and LuaJIT
  • 🧰 Prebuilt Binaries - Lua 5.4.8 included for Linux/macOS/Windows
  • 🔄 Bidirectional integration - Call Lua from JS and JS from Lua
  • 📦 Rich data exchange - Objects, arrays, functions in both directions
  • 🎯 TypeScript-ready - Full type definitions included
  • 🚀 Native performance - N-API bindings, no WebAssembly overhead

⚡ Quick Start

npm install lua-state
const { LuaState } = require("lua-state");

const lua = new LuaState();

lua.setGlobal("name", "World");
const result = lua.eval('return "Hello, " .. name');
console.log(result); // → "Hello, World"

📦 Installation

Prebuilt binaries are currently available for Lua 5.4.8 and downloaded automatically from GitHub Releases. If a prebuilt binary is available for your platform, installation is instant - no compilation required. Otherwise, it will automatically build from source.

Requires Node.js 18+, tar (system tool or npm package), and a valid C++ build environment (for node-gyp) if binaries are built from source.

Tip: if you only use prebuilt binaries you can reduce install size with npm install lua-state --no-optional.

🧠 Basic Usage

const lua = new LuaState();

Get Current Lua Version

console.log(lua.getVersion()); // "Lua 5.4.8"

Evaluate Lua Code

console.log(lua.eval("return 2 + 2")); // 4
console.log(lua.eval('return "a", "b", "c"')); // ["a", "b", "c"]

Share Variables

// JS → Lua
lua.setGlobal("user", { name: "Alice", age: 30 });

// Lua → JS
lua.eval("config = { debug = true, port = 8080 }");
console.log(lua.getGlobal("config")); // { debug: true, port: 8080 }
console.log(lua.getGlobal("config.port")); // 8080
console.log(lua.getGlobal("config.missing")); // undefined - path exists but field is missing
console.log(lua.getGlobal("missing")); // null - global variable does not exist at all

Call Functions Both Ways

// Call Lua from JS
lua.eval("function add(a, b) return a + b end");
const add = lua.getGlobal("add");
console.log(add(5, 7)); // 12

// Call JS from Lua
lua.setGlobal("add", (a, b) => a + b);
console.log(lua.eval("return add(3, 4)")); // 12

// JS function with multiple returns
lua.setGlobal("getUser", () => ["Alice", 30]);
lua.eval("name, age = getUser()");
console.log(lua.getGlobal("name")); // "Alice"
console.log(lua.getGlobal("age")); // 30

// JS function that throws an error
lua.setGlobal("throwError", () => {
  throw new Error("Something went wrong");
});
const result = lua.eval(`
  local ok, err = pcall(throwError);
  return { ok, err }
`);
console.log(result); // { 1: false, 2: "Error: Something went wrong" }

Get Table Length

lua.eval("items = { 1, 2, 3 }");
console.log(lua.getLength("items")); // 3

File Execution

-- config.lua
return {
  title = "My App",
  features = { "auth", "api", "db" }
}
const config = lua.evalFile("config.lua");
console.log(config.title); // "My App"

🕒 Execution Model

All Lua operations in lua-state are synchronous by design. The Lua VM runs in the same thread as JavaScript, providing predictable and fast execution. For asynchronous I/O, consider isolating Lua VMs in worker threads.

  • await is not required and not supported - calls like lua.eval() block until completion
  • Lua coroutines work normally within Lua, but are not integrated with the JavaScript event loop
  • Asynchronous bridging between JS and Lua is intentionally avoided to keep the API simple, deterministic, and predictable.

⚠️ Note: Lua 5.1 and LuaJIT have a small internal C stack, which may cause stack overflows when calling JS functions in very deep loops. Lua 5.1.1+ uses a larger stack and does not have this limitation.

🧩 API Reference

LuaState Class

new LuaState(options?: {
  libs?: string[] | null // Libraries to load, use null or empty array to load none (default: all)
})

Available libraries: base, bit32, coroutine, debug, io, math, os, package, string, table, utf8

Core Methods

| Method | Description | Returns | | ------------------------ | ------------------- | ------------------------------- | | eval(code) | Execute Lua code | LuaValue | | evalFile(path) | Run Lua file | LuaValue | | setGlobal(name, value) | Set global variable | this | | getGlobal(path) | Get global value | LuaValue \| null \| undefined | | getLength(path) | Get length of table | number \| null \| undefined | | getVersion() | Get Lua version | string |

🔄 Type Mapping (JS ⇄ Lua)

When values are passed between JavaScript and Lua, they’re automatically converted according to the tables below. Circular references are supported internally and won’t cause infinite recursion.

JavaScript → Lua

| JavaScript Type | Becomes in Lua | Notes | | --------------- | -------------- | --------------------------------------------------------------------------- | | string | string | UTF-8 encoded | | number | number | 64-bit double precision | | boolean | boolean | | | Date | number | Milliseconds since Unix epoch | | undefined | nil | | | null | nil | | | Function | function | Callable from Lua | | Object | table | Recursively copies enumerable fields. Non-enumerable properties are ignored | | Array | table | Indexed from 1 in Lua | | BigInt | string | |

Lua → JavaScript

| Lua Type | Becomes in JavaScript | Notes | | ---------- | --------------------- | ----------------------------------- | | string | string | UTF-8 encoded | | number | number | 64-bit double precision | | boolean | boolean | | | nil | null | | | table | object | Converts to plain JavaScript object | | function | function | Callable from JS |

⚠️ Note: Conversion is not always symmetrical - for example,
a JS Date becomes a number in Lua, but that number won’t automatically
convert back into a Date when returned to JS.

🧩 TypeScript Support

This package provides full type definitions for all APIs.
You can optionally specify the expected Lua value type for stronger typing and auto-completion:

import { LuaState } from "lua-state";

const lua = new LuaState();

const anyValue = lua.eval("return { x = 1 }"); // LuaValue | undefined
const numberValue = lua.eval<number>("return 42"); // number

🧰 CLI

npx lua-state install [options]

Options:

The build system is based on node-gyp and supports flexible integration with existing Lua installations.

| Option | Description | Default | | ----------------------------------------------- | ----------------------------------------- | ---------- | | -m, --mode | download, source, or system | download | | -f, --force | Force rebuild | false | | -v, --version | Lua version for download build | 5.4.8 | | --source-dir, --include-dirs, --libraries | Custom paths for source/system builds | - |

Examples:

# Rebuild with Lua 5.2.4
npx lua-state install --force --version=5.2.4

# Rebuild with system Lua
npx lua-state install --force --mode=system --libraries=-llua5.4 --include-dirs=/usr/include/lua5.4

# Rebuild with system or prebuilt LuaJIT
npx lua-state install --force --mode=system --libraries=-lluajit-5.1 --include-dirs=/usr/include/luajit-2.1

# Rebuild with custom lua sources
npx lua-state install --force --mode=source --source-dir=deps/lua-5.1/src

⚠️ Note: LuaJIT builds are only supported in system mode (cannot be built from source).

Run a Lua script file or code string with the CLI tool:

npx lua-state run [file]

Options:

| Option | Description | Default | | ----------------------- | --------------------------------------- | ------- | | -c, --code <code> | Lua code to run as string | - | | --json | Output result as JSON | false | | -s, --sandbox [level] | Run in sandbox mode (light, strict) | - |

Examples:

# Run a Lua file
npx lua-state run script.lua

# Run Lua code from string
npx lua-state run --code "print('Hello, World!')"

# Run and output result as JSON
npx lua-state run --code "return { name = 'Alice', age = 30 }" --json

# Run in sandbox mode (light restrictions)
npx lua-state run --sandbox light script.lua

# Run in strict sandbox mode (heavy restrictions)
npx lua-state run --sandbox strict script.lua

🌍 Environment Variables

These variables can be used for CI/CD or custom build scripts.

| Variable | Description | Default | | ----------------------- | ------------------------------------------- | ---------- | | LUA_STATE_MODE | Build mode (download, source, system) | download | | LUA_STATE_FORCE_BUILD | Force rebuild | false | | LUA_VERSION | Lua version (for download mode) | 5.4.8 | | LUA_SOURCE_DIR | Lua source path (for source mode) | - | | LUA_INCLUDE_DIRS | Include directories (for system mode) | - | | LUA_LIBRARIES | Library paths (for system mode) | - |

🔍 Compared to other bindings

| Package | Lua versions | TypeScript | API Style | Notes | | ------------- | -------------------- | ---------- | ------------------- | -------------------------------------------- | | fengari | 5.2 (WASM) | ❌ | Pure JS | Browser-oriented, slower | | lua-in-js | 5.3 (JS interpreter) | ✅ | Pure JS | No native performance | | wasmoon | 5.4 (WASM) | ✅ | Async/Promise | Node/Browser compatible | | node-lua | 5.1 | ❌ | Native (legacy NAN) | Outdated, Linux-only | | lua-native | 5.4 (N-API) | ✅ | Native N-API | Active project, no multi-version support | | lua-state | 5.1–5.4, LuaJIT | ✅ | Native N-API | Multi-version, prebuilt binaries, modern API |

⚡ Performance

Benchmarked on Lua 5.4.8 (Ryzen 7900X, Debian Bookworm, Node.js 24):

| Benchmark | Iterations | Time (ms) | | ------------------------ | ---------- | --------- | | Lua: pure computation | 1,000,000 | ≈ 3.8 | | JS → Lua calls | 50,000 | ≈ 4.3 | | Lua → JS calls | 50,000 | ≈ 6.4 | | JS → Lua data transfer | 50,000 | ≈ 135.0 | | Lua → JS data extraction | 50,000 | ≈ 62.5 |

To run the benchmark locally: npm run bench

🧪 Quality Assurance

Each native binary is built and tested automatically before release.
The test suite runs JavaScript integration tests to ensure stable behavior across supported systems.

🪪 License

MIT License © quaternion

🌐 GitHub📦 npm