@thigasdevelopment/luam
v1.1.0
Published
The Luam compiler for Multi Theft Auto resources.
Maintainers
Readme
Write .luam files with types, classes, enums and template strings. The
compiler checks them and emits readable Lua 5.1 plus a generated meta.xml —
a resource your MTA server can start as-is.
It is typed Lua, not TypeScript. Blocks still end with end, inequality is
still ~=, and comments use # and #* ... *#.
enum Target { LUA_51, MTA }
class Luam {
version: string
target: Target = Target.LUA_51
files: number = 0
constructor = function (version: string)
self.version = version
end
compile = function (source: string): string
self.files += 1
return `Luam ${self.version} compiled ${source} to plain Lua 5.1`
end
}
local luam = new Luam('0.18.0')
outputServerLog(luam:compile('src/server/main.luam'))Annotations are erased at build time. dxDrawText in a server file, a typo in an
MTA function name, a string where a number belongs — all build errors, before
the server starts. A build with any error writes nothing.
Install
Needs Node.js 20+ and an MTA:SA 1.5+ server. No Lua toolchain.
npm install --global @thigasdevelopment/luam
luam --versionThen install the VS Code extension — it runs the same checker as the CLI:
luam setup # detects your editors and asks before installing
luam doctor # verifies CLI, editors and extensionInstallation · Instalação —
PATHtroubleshooting,npx, install from source, manual.vsix.
Quick start
1. Scaffold. init writes exactly one file, .luam.manifest.
mkdir my-resource && cd my-resource
luam init2. Write some Luam. Create the source tree yourself — the folder decides
the environment: src/server, src/client, src/shared.
my-resource/
├── .luam.manifest
└── src/
├── shared/config.luam
├── server/main.luam
└── client/hud.luam3. Build.
luam check # diagnostics only, writes nothing
luam build # writes build/my-resourceAn error names the file, the line and the rule:
src/client/hud.luam:4:5 error check-environment-api: API "outputChatBox" is server-only and is not available in a "client" file.4. Run it. Copy build/my-resource into
<MTA Server>/mods/deathmatch/resources/, then refresh and
start my-resource in the server console.
5. Iterate. Name your MTA server once in a .luam.server beside the resource
and let dev build, sync, restart and stream logs on every save:
serverPath = 'C:/MTA Server'luam dev
luam dev --start-server # also starts and owns the local MTA processCommands
| Command | What it does |
| --- | --- |
| luam init | Scaffolds .luam.manifest and stops |
| luam check | Compiles and prints diagnostics. Writes nothing — this is the CI command |
| luam build | Writes the bundled resource into <build.output>/<folder>, plus a source map |
| luam dev | Build, sync, restart and watch, while following the server log |
| luam ensure | Build, sync and restart on every save |
| luam server | Run an existing local MTA server in the foreground |
| luam trace | Resolves a production error position back to the authored file |
| luam setup | Installs the editor extension |
| luam doctor | Reports CLI, Node.js, detected editors and extension status |
Exit codes: 0 success, 1 build errors, 2 invalid command line or
configuration. Progress goes to stderr and the report to stdout.
CLI commands · Comandos da CLI — every option and exit-code details.
The language
| Feature | Notes |
| --- | --- |
| Type annotations | Optionals, unions, arrays, aliases, generics, fun(string): void — all erased |
| Classes | extends, implements, constructor, super(...), new |
| Decorators | @Getter and @Setter generate typed accessors |
| Interfaces | Verified by the checker, never reach the generated Lua |
| Enums | Zero-based, checked members, erased when unused |
| Template strings | `Hi ${name:Guest}` — scope-checked, with defaults |
| Operators | +=, -=, *=, /=, ..=, and score++ / score-- as statements |
| Comments | # line and #* block *#; length without a space is #items |
| Object extensions | items.count, name.trim, ratio.clamp(a, b) |
| Multi-return | local x, y, z = getElementPosition(el) — typed from the MTA catalog |
| export | Erased from the Lua, written into meta.xml |
| Native libraries | sleep plus Threads, Async and Dotenv, injected only when named |
| MTA OOP classes | Player.getRandom(), File.exists(path), callable constructors |
| Deployment values | .env keys typed as env.SERVER_NAME, server-only |
| Strictness | #!strict (default), #!nonstrict, #!nocheck per file |
class, constructor, declare, enum, export, extends, implements,
interface, new and type are reserved on top of the Lua 5.1 keywords.
Property names still work (config.type), and type(value) keeps working.
Porting existing Lua? Rename to .luam, add #!nocheck, and annotate module by
module.
Environments
Every file is server, client or shared — from its folder, or from a #!
directive. That decides which MTA APIs resolve: dxDrawText in a client file is
fine, outputChatBox in the same file is a build error.
server and client may use shared declarations; shared may use only
shared; server and client never see each other. A name the catalog does
not know stays any, so a missing API never blocks a build.
The language · A linguagem — every feature, with the emitted Lua and the errors it catches.
Output
build/
├── my-resource.luam-map.json
└── my-resource/
├── meta.xml
├── config.lua
├── .env
├── assets/
└── src/
├── shared.lua
├── server.lua
└── client.luabuild ships at most one bundle per non-empty environment; config.lua, .env
and assets stay at their own paths, and the map stays outside the resource.
ensure defaults to a mirrored tree and dev always uses one.
Configuration
.luam.manifest is one table of five sections, and the folder that holds it names
the resource.
{
info = {
version = '1.0.0',
},
scripts = {
{ path = 'src/shared/**/*.luam', type = 'shared' },
{ path = 'src/server/**/*.luam', type = 'server' },
{ path = 'src/client/**/*.luam', type = 'client' },
},
files = {
'assets/**/*.png',
},
build = { output = 'build' },
}info carries the author, the version, the description and the dependencies;
environment carries oop, strict, the MTA version and the libraries; build
carries the output directory and the bundle, minify, map and obfuscate switches.
Order is position, and a blank line between two entries reaches meta.xml.
Editor support
The Luam VS Code extension runs a language server built on the same frontend
as the CLI, so the editor and the build never disagree: syntax highlighting,
diagnostics on every keystroke, scoped completion, hover types, go to
definition, find references and rename. It ships the Luam Dark and
Luam Light themes — pick one under File → Preferences → Theme → Color
Theme:
Supported and auto-installed by luam setup: VS Code, VS Code Insiders, Cursor,
VSCodium and Windsurf. The language server itself is editor-agnostic and speaks
--stdio to any LSP client.
Editors · Editores — compatibility matrix, manual
.vsixinstall, commands and settings.
Known limitations
- Narrowing follows a path, not an alias.
if self.value ~= nil thenrefines the field inside the block; storing the test in a variable does not carry it. - A class is a type everywhere, a value from its declaration —
extendsmay name a parent written further down, a top-levelnewmay not. - The MTA catalog can lag a release — a newer function stays
any. - Three metamethods stay blocked —
__index,__newindexand__call. Generic classes are supported. - A method the receiver does not declare is not reported —
counter:missing()keeps returningany; annotate the receiver to have the call checked. - The editor re-checks by declaration — a declaration change re-analyzes every file that can see it, an edit inside a function body only its own file.
- An export is named, never verified against the side that calls it.
- Type annotations are erased, so validate anything a client can send.
Limitations · Limitações — each one labelled planned, design boundary, upstream or platform constraint, with the workaround. The decisions behind the boundaries are recorded in
docs/adr.
Contributing
The repo is a pnpm workspace: compiler, cli, lsp, vscode, runtime,
mta-types and template.
pnpm install
pnpm typecheck
pnpm test
pnpm buildFork, open the pull request against develop, add a fixture and a snapshot for
new language behaviour, and run pnpm typecheck && pnpm test before sending it.
House style: TypeScript only, strict, no any, no comments inside code, 4-space
indentation, single quotes, kebab-case file names, path aliases instead of ../
imports, no barrel files, everything in English.
CONTRIBUTING.md is the full contract — the Node and Lua 5.1 requirements, the command that reproduces each required check, and what happens to a pull request from a fork. Conduct is in CODE_OF_CONDUCT.md. A vulnerability is reported privately through SECURITY.md, never as an issue.
The manual lives in docs/ — pnpm docs:dev to preview,
pnpm docs:verify before pushing. English is the source locale and pt-BR is
translated from it; CI fails when a page is missing from one.
Per-package docs: cli, lsp,
mta-types, vscode,
template. Releases are in the
changelog.
Acknowledgments
Multi Theft Auto — the execution platform.
Luau — the annotation syntax that keeps Lua looking
like Lua. lua-class and mta-threads — the runtimes behind class.lua and
threads.lua.
License
MIT © Thigas
