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 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-lua

v0.1.6

Published

Native Lua in React Native

Downloads

431

Readme

react-native-lua

An embedded Lua 5.4.9 runtime with LuaSocket for React Native 0.73.6 and newer. The same package supports the RN 0.73 legacy architecture and RN 0.83's New Architecture TurboModule/JSI installation path.

The public interpreter is a JSI HostObject in both architectures. Only the native binding installation changes; interpreter creation, Lua, LuaSocket, and the resource controls are shared.

Installation

pnpm add react-native-lua

Run pod install for iOS and rebuild the native app. Expo apps require a development/native build; Expo Go cannot load custom native code. Supported hosts are React Native >= 0.73.6, and the pod's iOS deployment floor is 13.4.

Usage

import {luaInterpreter, LUA_ERROR_CODE} from 'react-native-lua';

const lua = luaInterpreter({
  executionLimitMs: 2_000,
  memoryLimitBytes: 8 * 1024 * 1024,
  maxOutputBytes: 16 * 1024,
  maxOutputLines: 256,
  allowNetwork: true,
});

const status = lua.dostring(`
  local socket = require('socket')
  print(_VERSION, socket._VERSION)
`);

if (status === 0) {
  console.log(lua.getPrint());
} else if (status === LUA_ERROR_CODE.LUA_DEADLINE_EXCEEDED) {
  console.warn('script exceeded its deadline');
} else {
  console.warn(lua.getLatestError());
}

dostringasync and dofileasync retain their callback signatures. Lua runs on one owned native worker per interpreter, while JavaScript polls plain native result data. No JSI callback or runtime pointer crosses the worker boundary. New code can use executeStringAsync / executeFileAsync for a structured Promise result. Call cancel() to stop active work or destroy() to cancel, join, and close deterministically.

The HostObject also retains low-level Lua stack operations for advanced native and JavaScript callers (push*, getglobal, settable, type, and related methods).

LuaSocket and raw networking

LuaSocket is compiled into every platform binary and enabled by default. These in-memory modules are available:

  • socket, socket.core, socket.http, socket.ftp, socket.smtp, socket.url, socket.headers, and socket.tp;
  • mime, mime.core, ltn12, and mbox;
  • TCP, UDP, socket.select, and Unix stream/datagram sockets.

This is intentional raw socket access, not a wrapper around React Native's networking API. A script can connect, listen, send UDP datagrams, or access a Unix socket with the host app's OS privileges. No TLS implementation is bundled, so socket.http handles plain HTTP; HTTPS requires a separately provided LuaSec-compatible module.

Set allowNetwork: false when creating an interpreter to omit LuaSocket and its private require loader. A higher-level policy layer should use that option and expose separately approved network actions rather than run untrusted scripts with generic sockets.

When networking is enabled, require can resolve only compiled-in/preloaded modules. The global package table stays hidden, and filesystem and native dynamic module searchers are removed. Socket waits and socket.sleep are sliced so cancellation remains prompt and the interpreter deadline is enforced. Blocking OS DNS resolution runs on a detached helper that owns copied resolver inputs and its result; the Lua execution thread polls cancellation and the deadline and may return before that helper finishes. Large UDP/Unix-datagram receive buffers use Lua's capped allocator.

Security and resource boundaries

New interpreters expose Lua's base, coroutine, table, string, math, and UTF-8 libraries. io, os, package, and debug are not ambient globals. The native dofile / executeFileResult interpreter methods are denied unless allowFileSystem is true. The Lua global dofile remains unavailable even when file execution is enabled. Bytecode is rejected unless allowBytecode is true.

The bundled Lua source removes os.execute and the other process/environment/ filesystem-mutating OS functions. They remain absent even if a native integrator explicitly opens the reduced OS library.

Each interpreter has:

  • a monotonic execution deadline checked by a Lua instruction hook and socket wait integration;
  • a capped Lua allocator (32 MiB by default, 512 KiB minimum);
  • bounded captured output (64 KiB and 1,000 lines by default);
  • explicit cancellation and destruction;
  • text-only loading by default.

Run the native security, resource, and loopback networking suite with:

pnpm test:native

Curated capability injection

The generic engine intentionally contains no application-specific command namespace. A higher-level native adapter can construct SKRNLuaInterpreter, set allowNetwork: false, and call withState(...) before exposure to install only explicitly granted Lua C functions/tables.

Keep policy, permission prompts, command schemas, and host-action dispatch in that adapter. Do not expose arbitrary NativeModules, process APIs, dynamic loaders, or raw filesystem/network access to untrusted scripts.

RN 0.73.6 old-architecture example

example/ is a real RN 0.73.6 app with newArchEnabled=false. It autolinks this checkout, creates the actual HostObject, loads LuaSocket helpers, runs Lua coroutines, and verifies that unsafe ambient libraries remain unavailable.

The example has its own RN 0.73 dependency tree, but this workspace also installs the root RN 0.83 development toolchain. Use Node 20.19.4 or newer for the root-level pnpm commands below (Node 22 is also supported):

pnpm install
pnpm pods
pnpm example:ios
# or: pnpm example:android

License

MIT. The bundled LuaSocket sources are also MIT licensed and retain their upstream copyright notices.

Bulk value transfer (unreleased)

Use the synchronous native bulk-value API instead of walking Lua tables field by field through pushstring / rawget / tonumber / pop from JavaScript. The stack-facing terminology follows Lua itself: values are pushed and globals are set.

import {luaInterpreter, type LuaValue} from 'react-native-lua';

const lua = luaInterpreter();
await lua.executeStringAsync(`
  output = {title = "Example", samples = {1, 2, 3}}
  status = "ready"
`);
const {output, status} = lua.readGlobals(['output', 'status'] as const);
// output and status are LuaValue; narrow/validate your application schema.

lua.getglobal('output');
const [first, second] = lua.readValues([-1, -1] as const);
lua.pop(1); // readValues itself does not change the stack

const one: LuaValue = lua.readGlobal('output');

// Host -> Lua, still one native traversal rather than per-field stack calls.
lua.pushValue({message: 'hello', samples: [1, 2, 3]});
lua.pushValues([42, 'ready']);

lua.setGlobal('response', {ok: true});
lua.setGlobals({status: 'ready', attempts: 3});

readValues, readGlobals, readValue, and readGlobal each enter the native reader once. pushValue, pushValues, setGlobal, and setGlobals likewise perform one native input conversion rather than recursively calling the low-level stack API from JavaScript. Reads use raw Lua access; global sets use raw assignment, so _G.__index / _G.__newindex are not invoked.

Reads return detached data, not live Lua references. Push failures restore the original stack exactly. setGlobals snapshots and materializes the entire batch before assignment and rolls back already-applied names if native assignment fails. Busy/destroyed interpreters reject every bulk transfer; nothing waits for or interrupts a running worker. Keep your existing per-interpreter execution queue around execution plus any related read/push/set operation.

The transferable subset is null (Lua nil), booleans, finite numbers, valid UTF-8 strings, dense arrays and string-keyed maps. Read maps have null prototypes, so __proto__ is an ordinary data key. Host -> Lua object input accepts only plain objects with Object.prototype or null prototypes; Map, Set, typed arrays, class instances and other non-plain objects reject instead of silently losing data. Safe integral JS numbers become Lua integers; fractional/unsafe-integral values and negative zero remain Lua floats. Functions, userdata, coroutines, cycles, mixed keys and sparse arrays are rejected. Shared acyclic Lua tables are copied. Unsafe Lua integers are rejected by readers instead of silently rounded.

Lua cannot distinguish an empty array from an empty map: {} becomes an object by default; pass {emptyTables: 'array'} to select arrays for empty tables.

Limits cover the whole call: maxDepth (default 32, maximum 64), maxEntries (default 16,384, maximum 262,144), and maxStringBytes (default 1 MiB, maximum 16 MiB; includes requested global names, table keys and values). Up to 256 roots may be requested. Lower limits, including zero, are supported.

This is a native library addition: rebuild the consuming app after upgrading. No changes to Lua script syntax or callbacks are required. See bulk reader semantics and tests and push/set semantics.