https://github.com/fatal10110/lua-redis-wasm
WebAssembly-based Redis Lua 5.1 script engine for Node.js. Execute Redis-compatible Lua scripts without a live Redis server. Perfect for testing, sandboxing, and local development.
https://github.com/fatal10110/lua-redis-wasm
embedded-lua lua nodejs redis redis-scripts typescript wasm
Last synced: 3 months ago
JSON representation
WebAssembly-based Redis Lua 5.1 script engine for Node.js. Execute Redis-compatible Lua scripts without a live Redis server. Perfect for testing, sandboxing, and local development.
- Host: GitHub
- URL: https://github.com/fatal10110/lua-redis-wasm
- Owner: fatal10110
- License: mit
- Created: 2026-01-15T06:33:27.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-01-17T22:05:20.000Z (3 months ago)
- Last Synced: 2026-01-18T16:16:57.845Z (3 months ago)
- Topics: embedded-lua, lua, nodejs, redis, redis-scripts, typescript, wasm
- Language: TypeScript
- Homepage:
- Size: 136 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# lua-redis-wasm
[](https://www.npmjs.com/package/lua-redis-wasm)
[](https://github.com/fatal10110/lua-redis-wasm/actions)
[](https://nodejs.org)
[](LICENSE)
A WebAssembly-based Redis Lua 5.1 script engine for Node.js. Execute Redis-compatible Lua scripts in JavaScript/TypeScript environments without a live Redis server.
## Features
- **Redis-compatible Lua 5.1** - Uses the exact Lua version embedded in Redis
- **Binary-safe** - Full support for null bytes in scripts, arguments, and return values
- **Host integration** - Implement `redis.call`, `redis.pcall`, and `redis.log` in JavaScript
- **Resource limits** - Fuel-based instruction limiting, reply size caps, and memory coordination
- **Redis standard libraries** - Includes `cjson`, `cmsgpack`, `struct`, and `bit` modules
- **TypeScript support** - Full type definitions included
## Installation
```bash
npm install lua-redis-wasm
```
**Requirements:** Node.js >= 22
## Quick Start
```typescript
import { LuaWasmEngine } from "redis-lua-wasm";
const engine = await LuaWasmEngine.create({
host: {
redisCall(args) {
const cmd = args[0].toString();
if (cmd === "PING") return { ok: Buffer.from("PONG") };
if (cmd === "GET") return Buffer.from("value");
return { err: Buffer.from("ERR unknown command") };
},
redisPcall(args) {
return this.redisCall(args);
},
log(level, message) {
console.log(`[${level}] ${message.toString()}`);
},
},
});
// Simple evaluation
const result = engine.eval("return 1 + 1"); // Returns: 2
// With KEYS and ARGV
const data = engine.evalWithArgs(
"return {KEYS[1], ARGV[1]}",
[Buffer.from("user:1")],
[Buffer.from("hello")],
);
```
## API
### LuaWasmEngine.create(options)
Creates a new engine instance with host integration.
```typescript
const engine = await LuaWasmEngine.create({
host: RedisHost, // Required: host callbacks
limits?: EngineLimits, // Optional: resource limits
wasmPath?: string, // Optional: custom WASM file path
wasmBytes?: Uint8Array // Optional: pre-loaded WASM binary
});
```
### LuaWasmEngine.createStandalone(options)
Creates an engine without host integration. `redis.call` and `redis.pcall` return errors.
```typescript
const engine = await LuaWasmEngine.createStandalone({});
engine.eval("return math.sqrt(16)"); // Works
engine.eval("return redis.call('PING')"); // Returns error
```
### engine.eval(script)
Evaluates a Lua script and returns the result.
```typescript
engine.eval("return 'hello'"); // Returns: Buffer.from("hello")
engine.eval("return {1, 2, 3}"); // Returns: [1, 2, 3]
```
### engine.evalWithArgs(script, keys, args)
Evaluates a script with binary-safe `KEYS` and `ARGV` arrays.
```typescript
engine.evalWithArgs(
"return {KEYS[1], ARGV[1], ARGV[2]}",
[Buffer.from("key:1")],
[Buffer.from("arg1"), Buffer.from("arg2\x00with-null")],
);
```
### LuaWasmEngine (Convenience)
Alternative API that combines loading and creation.
#### LuaWasmEngine.create(options)
```typescript
const engine = await LuaWasmEngine.create({ host: myHost });
```
#### LuaWasmEngine.createStandalone(options)
```typescript
const engine = await LuaWasmEngine.createStandalone();
```
## Host Interface
The host must implement three callbacks:
```typescript
type RedisHost = {
redisCall: (args: Buffer[]) => ReplyValue; // For redis.call()
redisPcall: (args: Buffer[]) => ReplyValue; // For redis.pcall()
log: (level: number, message: Buffer) => void; // For redis.log()
};
```
### redisCall
Called when Lua executes `redis.call(...)`. Arguments arrive as `Buffer[]`. Throw an error to return it to Lua.
### redisPcall
Called when Lua executes `redis.pcall(...)`. Return `{ err: Buffer }` instead of throwing to match Redis behavior.
### log
Called when Lua executes `redis.log(level, message)`. Level is a numeric Redis log level.
## Reply Types
Return values are Redis-compatible:
```typescript
type ReplyValue =
| null // Lua nil
| number // Integer (safe range)
| bigint // Integer (64-bit)
| Buffer // Bulk string
| { ok: Buffer } // Status reply (+OK)
| { err: Buffer } // Error reply (-ERR)
| ReplyValue[]; // Array
```
### Determining the Response Type
Use type guards to inspect what Lua returned:
```typescript
const result = engine.eval(script);
// Check for null (Lua nil)
if (result === null) {
console.log("Got nil");
}
// Check for integer
else if (typeof result === "number" || typeof result === "bigint") {
console.log("Got integer:", result);
}
// Check for array (Lua table with sequential keys)
else if (Array.isArray(result)) {
console.log("Got array with", result.length, "elements");
for (const item of result) {
// Each element is also a ReplyValue - handle recursively
}
}
// Check for status reply ({ok: Buffer}) - e.g. from SET, PING
else if (typeof result === "object" && "ok" in result) {
console.log("Got status:", result.ok.toString());
}
// Check for error reply ({err: Buffer})
else if (typeof result === "object" && "err" in result) {
console.log("Got error:", result.err.toString());
}
// Otherwise it's a bulk string (Buffer)
else if (Buffer.isBuffer(result)) {
console.log("Got bulk string:", result.toString());
}
```
### Lua Type Conversions
This matches Redis Lua behavior:
```typescript
// Lua nil → null
engine.eval("return nil"); // null
// Lua number → number (or bigint for large values)
engine.eval("return 42"); // 42
engine.eval("return 2^62"); // 4611686018427387904n (bigint)
// Lua string → Buffer
engine.eval("return 'hello'"); // Buffer.from("hello")
// Lua table (array) → ReplyValue[]
engine.eval("return {1, 2, 3}"); // [1, 2, 3]
engine.eval("return {'a', 'b'}"); // [Buffer, Buffer]
// Status reply: commands like SET, PING return {ok: "..."}
// In Lua: local resp = redis.call('SET', 'k', 'v') → resp.ok == "OK"
engine.eval("return redis.call('SET', 'k', 'v')"); // { ok: Buffer.from("OK") }
engine.eval("return redis.call('SET', 'k', 'v').ok"); // Buffer.from("OK")
// Error reply: redis.pcall catches errors as {err: "..."}
// In Lua: local resp = redis.pcall('INVALID') → resp.err == "ERR ..."
engine.eval("return redis.pcall('INVALID')"); // { err: Buffer.from("ERR ...") }
```
> **Note**: Status replies (`+OK`) become `{ok: "..."}` tables in Lua, matching real Redis behavior.
> Use `resp.ok` to access the status string.
## Resource Limits
Protect against runaway scripts with configurable limits:
```typescript
const module = await load({
limits: {
maxFuel: 10_000_000, // Instruction budget
maxMemoryBytes: 64 * 1024 * 1024, // Memory cap (host-coordinated)
maxReplyBytes: 2 * 1024 * 1024, // Max reply size
maxArgBytes: 1 * 1024 * 1024, // Max single argument size
},
});
const engine = module.create(host);
```
| Limit | Description | Enforcement |
| ---------------- | ---------------------------- | ---------------- |
| `maxFuel` | Instruction count budget | WASM runtime |
| `maxMemoryBytes` | Memory growth cap | Host-coordinated |
| `maxReplyBytes` | Maximum reply payload size | WASM runtime |
| `maxArgBytes` | Maximum single argument size | WASM runtime |
## Included Lua Libraries
The engine includes Redis-standard Lua modules:
- **cjson** - JSON encoding/decoding
- **cmsgpack** - MessagePack serialization
- **struct** - Binary data packing/unpacking
- **bit** - Bitwise operations
Plus standard Lua 5.1 libraries: `base`, `table`, `string`, `math`.
## Use Cases
- **Testing** - Unit test Redis Lua scripts without a Redis server
- **Sandboxing** - Execute untrusted Lua with resource limits
- **Development** - Rapid iteration on Lua scripts locally
- **Embedding** - Add Redis-compatible scripting to Node.js applications
## Compatibility
| Feature | Status |
| ------------------------------- | ------- |
| Redis version target | 7.x |
| Lua version | 5.1 |
| Binary-safe strings | Yes |
| `redis.call` / `redis.pcall` | Yes |
| `redis.log` | Yes |
| `redis.sha1hex` | Yes |
| Standard Lua libraries | Yes |
| Redis Lua modules (cjson, etc.) | Yes |
| Debug / REPL helpers | No |
| Redis Modules API | Not yet |
## Building from Source
```bash
# Build everything (requires Emscripten via Docker)
npm run build
# Build steps individually
npm run build:wasm # Compile C to WASM
npm run build:ts # Compile TypeScript
# Run tests
npm test
npm run test:skip-wasm # Skip WASM rebuild
```
## Documentation
- [Host Interface Contract](docs/host-interface.md)
- [Binary ABI Specification](docs/abi.md)
- [Limits and Compatibility](docs/limits-compat.md)
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on:
- How to report bugs
- How to suggest enhancements
- Development setup
- Pull request process
- Coding standards
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
## Security
Security is important to us. If you discover a security vulnerability, please follow our [Security Policy](SECURITY.md) for responsible disclosure.
For general security considerations when using lua-redis-wasm, see the [Security Guide](SECURITY.md#security-considerations).
## Support
- **Issues**: [GitHub Issues](https://github.com/fatal10110/lua-redis-wasm/issues)
- **Discussions**: [GitHub Discussions](https://github.com/fatal10110/lua-redis-wasm/discussions)
- **Documentation**: [docs/](docs/)
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each release.
## License
This package is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
### Third-Party Licenses
This project includes third-party code, all under the MIT License:
- **Lua 5.1** - Copyright (C) 1994-2012 Lua.org, PUC-Rio
- **lua_cjson** - Copyright (C) 2010-2012 Mark Pulford
- **lua_cmsgpack** - Copyright (C) 2012 Salvatore Sanfilippo
- **lua_struct** - Copyright (C) 2010-2018 Lua.org, PUC-Rio
- **lua_bit** - Copyright (C) 2008-2012 Mike Pall
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for full license texts.
## Acknowledgments
- Redis team for the Lua integration design
- Emscripten project for WebAssembly tooling
- Contributors and maintainers of the included Lua libraries