# The Roblox Library

Lune has a powerful standard library and set of APIs for manipulating Roblox place files and model files. It contains APIs for reading & writing files, and gives you instances to use, just as if you were scripting inside of the Roblox engine, albeit with a more limited API.

  * For examples on how to write Roblox-specific Lune scripts, check out the [Examples](https://lune-org.github.io/docs/roblox/2-examples) page.
  * For a guide on how to migrate to Lune from [Remodel](https://github.com/rojo-rbx/remodel), check out the [Migrating from Remodel](https://lune-org.github.io/docs/roblox/3-remodel-migration) page.
  * For a list of the currently implemented Roblox APIs, check out the [API Status](https://lune-org.github.io/docs/roblox/4-api-status) page.


# Modules

At this point you know how the most important standard libraries in Lune work and how to use them - and your code may be getting longer and more difficult to read.

Modularizing your code and splitting it across several files in Lune is different from other versions of Lua, and more similar to how things work in other languages such as JavaScript.

## File Structure

Section titled “File Structure”

Let’s start with a typical module setup that we’ll use throughout this chapter:

  * main.luau
  * sibling.luau
  * Directorydirectory
    * init.luau
    * child.luau

This structure shows the two main patterns you’ll use - individual module files (`sibling.luau`) and directory modules (`modules/` with its `init.luau`). The contents of these files are not very important for this article, but here is an example for the sake of completeness:

  * Main File
  * Sibling File
  * Directory Module (init)
  * Child Module

main.luau

    local sibling = require("./sibling")

    local directory = require("./directory")

    print(sibling.Hello) --> World

    print(directory.Child.Foo) --> Bar

    print(directory.Child.Fizz) --> Buzz

    print(directory.Sibling.Hello) --> World

sibling.luau

    return {

      Hello = "World",

directory/init.luau

    return {

      Child = require("@self/child"),

      Sibling = require("../sibling"),

directory/child.luau

    return {

      Foo = "Bar",

      Fizz = "Buzz",

## How Does It Work?

Section titled “How Does It Work?”

Looking at our main file, you’ll notice the require paths always start with `./` or `../`. This means “relative to the current file”, the same way it does in your terminal. When `main.luau` requires `"./sibling"`, Lune looks for `sibling.luau` in the same directory as `main`.

The interesting part is `require("./modules")`. Lune sees this is a directory and automatically looks for `modules/init.luau`. Inside that init file, we use two different types of require statements:

The statement `require("@self/child")` uses the special `@self` alias. Since init files represent their parent directory, `@self` here means - inside the “modules” directory. Without it, `require("./child")` would look for `child.luau` _next to the “modules” directory_ , not inside it.

## Coming from Other Languages

Section titled “Coming from Other Languages”

If you’re arriving at Lune with experience in other runtimes & languages, these comparisons may help you get oriented. If you want to get right into the nitty-gritty details, feel free to skip this section completely.

  * Lua 5.x
  * JavaScript / TypeScript
  * Python
  * Rust

**Traditional Lua Structure:**

  * main.lua
  * mylib.lua
  * Directoryutils/
    * init.lua
    * helper.lua

main.lua

    -- Lua 5.x - requires are relative to the working directory

    -- You need to configure package.path:

    package.path = package.path .. ";./utils/?.lua"

    local mylib = require("mylib")         -- Only works if CWD is correct

    local utils = require("utils")         -- Needs package.path setup

    local helper = require("utils.helper") -- Uses dots, not slashes

main.luau

    -- Lune - requires are relative to the file

    local mylib = require("./mylib")         -- Always works

    local utils = require("./utils")         -- No path config needed

    local helper = require("./utils/helper") -- Uses slashes like paths

The main difference here is that, in traditional Lua, requires depend on where you run the script from. In Lune, requires are relative to the file containing them, making your code portable and predictable.

**JavaScript / TypeScript Structure:**

  * package.json
  * index.js
  * Directorylib/
    * index.js
    * helper.js
  * Directorynode_modules/
    * Directoryexpress/
      * …

**Equivalent in Lune:**

  * main.luau
  * Directorylib/
    * init.luau
    * helper.luau

main.js

    // JavaScript

    const express = require('express')      // From node_modules

    const lib = require('./lib')            // Local file

    const helper = require('./lib/helper')  // Specific file

main.luau

    -- Lune has no centralized package management, yet...

    local lib = require("./lib")           -- Same pattern

    local helper = require("./lib/helper") -- Same pattern

File-relative requires are familiar and work the same way. The difference here is package management and dependency resolution. JavaScript has standardized on `node_modules` for package management, and there is no standardized package management solution in Lune yet.

**Python Structure:**

  * main.py
  * Directorymypackage/
    * `__init__.py`
    * module.py
    * Directorysubpackage/
      * `__init__.py`
      * helper.py

**Equivalent in Lune:**

  * main.luau
  * Directorymypackage/
    * init.luau
    * module.luau
    * Directorysubpackage/
      * init.luau
      * helper.luau

main.py

    # Python - many ways to import modules

    import mypackage

    from mypackage import module

    from mypackage.subpackage import helper

    import mypackage.module as mod

main.luau

    -- Lune - one single way to import modules

    local mypackage = require("./mypackage")

    local module = require("./mypackage/module")

    local helper = require("./mypackage/subpackage/helper")

    local mod = require("./mypackage/module") -- Aliasing via assignment

**Rust Structure:**

  * Cargo.toml
  * Directorysrc/
    * main.rs
    * lib.rs
    * Directoryutils/
      * mod.rs
      * helper.rs

**Equivalent in Lune:**

  * main.luau
  * lib.luau
  * Directoryutils/
    * init.luau
    * helper.luau

main.rs

    mod lib;

    mod utils;

    use crate::utils::helper;

    use lib::something;

main.luau

    local lib = require("./lib")

    local utils = require("./utils")

    -- No use statements - access through the module using simple dot notation

    local result = utils.helper.doSomething()

    local thing = lib.something

Like Rust, `init.luau` is your `mod.rs`. Unlike Rust, there’s no visibility modifiers or explicit module declarations - if you return a value, it is always public.

## Module Caching

Section titled “Module Caching”

Every module you require gets cached on the first call to the `require` function. This means that it is safe to store state within modules, and expose it using public functions:

counter.luau

    local count = 0

    return {

        increment = function()

            count += 1

            return count

        end

main.luau

    local counter1 = require("./counter")

    local counter2 = require("./counter")

    print(counter1.increment()) --> 1

    print(counter2.increment()) --> 2 (same table & function pointer!)

    print(counter1 == counter2)  --> true

This caching behavior is usually what you want - it prevents duplicate initialization and lets modules maintain internal state. Just remember that if you need separate instances of a class or something similar, you’ll need to return a function that creates its own, separate state.

Extra: Async Caching

Lune actually has an extra trick up its sleeve - it caches modules properly even if they call asynchronous functions during initialization! This lends itself to some very useful patterns - such as reading configuration files using the asynchronous `@lune/fs` standard library during `require`. You can have a single module that handles reading configuration files, and require it concurrently from multiple files, without worrying about race conditions or the configuration being read more than once.

## Path Resolution

Section titled “Path Resolution”

Lune keeps path resolution simple and predictable. Paths are case-sensitive on all platforms (even Windows) and always use forward slashes. When you require `"./myModule"`, Lune checks for:

  1. `myModule.luau` (preferred extension)
  2. `myModule.lua` (for compatibility)
  3. `myModule/init.luau` (directory module)
  4. `myModule/init.lua` (directory module, compatibility)

The search behavior is also consistent across all platforms.

## Configuring Aliases Using `.luaurc`

Section titled “Configuring Aliases Using .luaurc”

Lune supports standardized Luau configuration files that can define aliases and other settings for your project. To use aliases, you will need to create a JSON-like configuration file named `.luaurc` inside of a directory, as such:

.luaurc

      "aliases": {

        "utils": "./src/utilities",

        "config": "./configuration"

With these aliases defined, you can use them anywhere in your project, using the `@` prefix:

script.luau

    -- Instead of long relative paths ...

    local config = require("../../../configuration/settings")

    local helper = require("../../src/utilities/helper")

    -- ...you can use aliases!

    local config = require("@config/settings")

    local helper = require("@utils/helper")

It is also possible to create multiple `.luaurc` configuration files in your project. When Lune looks for a `.luaurc` file, it searches from your script’s directory up through parent directories. This means you can have project-wide configuration at the root, and override specific settings in subdirectories if necessary.

## What’s Next?

Section titled “What’s Next?”

You now have all the tools to organize your Lune scripts into clean, reusable modules. You can split code into files, create module hierarchies with directories, and you understand how Lune’s caching mechanism and path resolution work.

But, what happens when you need functionality that Lune doesn’t provide? Sometimes the best solution isn’t to rewrite something in Luau - it’s to use existing tools on your system. Let’s extend Lune’s capabilities by [Spawning Programs](https://lune-org.github.io/docs/the-book/7-modules/8-spawning-programs) next.


# Regex

Built-in library for regular expressions

#### Example usage

Section titled “Example usage”

    local Regex = require("@lune/regex")

    local re = Regex.new("hello")

    if re:isMatch("hello, world!") then

      print("Matched!")

    end

    local caps = re:captures("hello, world! hello, again!")

    print(#caps) -- 2

    print(caps:get(1)) -- "hello"

    print(caps:get(2)) -- "hello"

    print(caps:get(3)) -- nil

## Constructors

Section titled “Constructors”

### new

Section titled “new”

Creates a new `Regex` from a given string pattern.

#### Errors

Section titled “Errors”

This constructor throws an error if the given pattern is invalid.

#### Parameters

Section titled “Parameters”

  * `pattern` `string` The string pattern to use

#### Returns

Section titled “Returns”

  * `Regex` The new Regex object

## Methods

Section titled “Methods”

### isMatch

Section titled “isMatch”

Check if the given text matches the regular expression.

This method may be slightly more efficient than calling `find` if you only need to know if the text matches the pattern.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `text` `string` The text to search

#### Returns

Section titled “Returns”

  * `boolean` Whether the text matches the pattern

### find

Section titled “find”

Finds the first match in the given text.

Returns `nil` if no match was found.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `text` `string` The text to search

#### Returns

Section titled “Returns”

  * `RegexMatch?` The match object

### captures

Section titled “captures”

Finds all matches in the given text as a `RegexCaptures` object.

Returns `nil` if no matches are found.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `text` `string` The text to search

#### Returns

Section titled “Returns”

  * `RegexCaptures?` The captures object

### split

Section titled “split”

Splits the given text using the regular expression.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `text` `string` The text to split

#### Returns

Section titled “Returns”

  * `{ string }` The split text

### replace

Section titled “replace”

Replaces the first match in the given text with the given replacer string.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `haystack` `string` The text to search

  * `replacer` `string` The string to replace matches with

#### Returns

Section titled “Returns”

  * `string` The text with the first match replaced

### replaceAll

Section titled “replaceAll”

Replaces all matches in the given text with the given replacer string.

#### Parameters

Section titled “Parameters”

  * `self` Regex

  * `haystack` `string` The text to search

  * `replacer` `string` The string to replace matches with

#### Returns

Section titled “Returns”

  * `string` The text with all matches replaced

# RegexMatch

Section titled “RegexMatch”

A match from a regular expression.

Contains the following values:

  * `start` — The start index of the match in the original string.
  * `finish` — The end index of the match in the original string.
  * `text` — The text that was matched.
  * `len` — The length of the text that was matched.

# RegexCaptures

Section titled “RegexCaptures”

Captures from a regular expression.


# The Task Scheduler

Lune is built around a task scheduler, which can let you run things at fixed intervals, ensure some work happens after everything else is already done, and more.

The task scheduler is the backbone of Lune, and lets you handle structured concurrency. It is implemented using lightweight Lua threads / coroutines, and has **strong ordering guarantees**.

## Ordering

Section titled “Ordering”

The main purpose of the task scheduler is to ensure consistent ordering, and to let you prioritize work on three different levels by using the `task` standard library:

  1. **Immediate**: Tasks that should run immediately can be spawned using `task.spawn`.
  2. **Deferred**: Tasks that should run after all immediate tasks have finished can be spawned using `task.defer`.
  3. **Delayed**: Tasks that should run after a certain amount of time has passed can be spawned using `task.delay`.

Advanced: Runtime-Controlled Threads & Prioritization

These are user-facing concepts, but perhaps more interesting, is that Lune _**prioritizes Lua threads**_ over runtime-spawned tasks, such as those for incoming requests in `net.serve`.

This means that, in real world scenarios such as handling incoming requests in an HTTP server, the scheduler will ensure that your existing tasks are not starved of resources, and are always prioritized over handling new requests, for maximum throughput & lowest possible latency.

## Example Usage

Section titled “Example Usage”

### Spawning Tasks & Waiting

Section titled “Spawning Tasks & Waiting”

This example script will run several tasks concurrently, in lightweight Lua threads, also known as coroutines:

basic-tasks.luau

    local task = require("@lune/task")

    print("Hello, scheduler!")

    task.spawn(function()

      print("Spawned a task that will run instantly but not block")

      task.wait(2)

      print("The instant task resumed again after 2 seconds")

    end)

    print("Spawning a delayed task that will run after 5 seconds")

    task.delay(5, function()

      print("Waking up from my deep slumber...")

      task.wait(1)

      print("Hello again!")

      task.wait(1)

      print("Goodbye again! 🌙")

    end)

### Deferring Work

Section titled “Deferring Work”

This example script runs a bit of work after all other threads have finished their work or are yielding waiting for some other result:

deferred-tasks.luau

    local task = require("@lune/task")

    task.defer(function()

      print("All the scheduled work has finished, let's do some more!")

      local a = 0

      for _ = 1, 100000 do

        local b = a + 1

      end

      print("Phew, that was tough.")

    end)

    print("Working...")

    local s = ""

    for _ = 1, 5000 do

      s ..= ""

    end

    print("Done!")

### Advanced Usage & Async

Section titled “Advanced Usage & Async”

Spawning tasks like this can be very useful together with asynchronous APIs from other standard libraries, such as [`net.request`](https://lune-org.github.io/docs/api-reference/net.md#request):

async-tasks.luau

    local net = require("@lune/net")

    local task = require("@lune/task")

    local completed = false

    task.spawn(function()

      while not completed do

        print("Waiting for response...")

        task.wait() -- Wait the minimum amount possible

      end

      print("No longer waiting!")

    end)

    print("Sending request")

    net.request("https://google.com")

    print("Got response")

    completed = true

Bonus

### Barebones Signal Implementation

Section titled “Barebones Signal Implementation”

Using the task library, it becomes trivial to implement signal objects that take callbacks to run when a signal is fired, and that can handle both synchronous and yielding (async) callbacks without additional complexity:

signals.luau

    local task = require("@lune/task")

    local function newSignal()

      local callbacks = {}

      local function connect(callback: (...any) -> ())

        table.insert(callbacks, callback)

      end

      local function fire(...: any)

        for _, callback in callbacks do

          task.spawn(callback, ...)

        end

      end

      return connect, fire

    end

    local connectToThing, fireThing = newSignal()

    connectToThing(function(value)

      print("Callback #1 got value:", value)

      task.wait(1)

      print("Callback #1 still has value:", value)

    end)

    connectToThing(function(value)

      print("Callback #2 got value:", value)

      task.wait(0.5)

      print("Callback #2 still has value:", value)

    end)

    print("Before firing")

    fireThing(123)

    print("After firing")

    --> Before firing

    --> Callback #1 got value: 123

    --> Callback #2 got value: 123

    --> After firing

    --> ...

    --> Callback #2 still has value: 123

    --> ...

    --> Callback #1 still has value: 123

## Conclusion

Section titled “Conclusion”

Congratulations! You’ve completed The Lune Book and now have all the tools you need to build powerful scripts with Lune.

You’ve learned how to work with files, make network requests, handle user input, organize code into modules, spawn external processes, and how to wrangle the task scheduler. More importantly, you’ve seen how these pieces work together to create scripts that are both simple to write and capable of handling complex real-world problems.

The API reference in the sidebar contains detailed documentation for all of Lune’s capabilities, and the community is always ready to help if you get stuck.

Now go build! 🚀


# Luau

Built-in library for generating luau bytecode & functions.

#### Example usage

Section titled “Example usage”

    local luau = require("@lune/luau")

    local bytecode = luau.compile("print('Hello, World!')")

    local callableFn = luau.load(bytecode)

    -- Additionally, we can skip the bytecode generation and load a callable function directly from the code itself.

    -- local callableFn = luau.load("print('Hello, World!')")

    callableFn()

Since luau bytecode is highly compressible, it may also make sense to compress it using the `serde` library while transmitting large amounts of it.

## Functions

Section titled “Functions”

### compile

Section titled “compile”

Compiles sourcecode into Luau bytecode

An error will be thrown if the sourcecode given isn’t valid Luau code.

#### Example usage

Section titled “Example usage”

    local luau = require("@lune/luau")

    -- Compile the source to some highly optimized bytecode

    local bytecode = luau.compile("print('Hello, World!')", {

      optimizationLevel = 2,

      coverageLevel = 0,

      debugLevel = 1,

    })

#### Parameters

Section titled “Parameters”

  * `source` The string that will be compiled into bytecode

  * `compileOptions` The options passed to the luau compiler that will output the bytecode

#### Returns

Section titled “Returns”

  * luau bytecode

### load

Section titled “load”

Generates a function from either bytecode or sourcecode

An error will be thrown if the sourcecode given isn’t valid luau code.

#### Example usage

Section titled “Example usage”

    local luau = require("@lune/luau")

    local bytecode = luau.compile("print('Hello, World!')")

    local callableFn = luau.load(bytecode, {

      debugName = "'Hello, World'"

    })

    callableFn()

#### Parameters

Section titled “Parameters”

  * `source` Either luau bytecode or string source code

  * `loadOptions` The options passed to luau for loading the chunk

#### Returns

Section titled “Returns”

  * luau chunk

## Types

Section titled “Types”

### CompileOptions

Section titled “CompileOptions”

The options passed to the luau compiler while compiling bytecode.

This is a dictionary that may contain one or more of the following values:

  * `optimizationLevel` \- Sets the compiler option “optimizationLevel”. Defaults to `1`.
  * `coverageLevel` \- Sets the compiler option “coverageLevel”. Defaults to `0`.
  * `debugLevel` \- Sets the compiler option “debugLevel”. Defaults to `1`.

Documentation regarding what these values represent can be found [here](https://github.com/Roblox/luau/blob/bd229816c0a82a8590395416c81c333087f541fd/Compiler/include/luacode.h#L13-L39).

### LoadOptions

Section titled “LoadOptions”

The options passed while loading a luau chunk from an arbitrary string, or bytecode.

This is a dictionary that may contain one or more of the following values:

  * `debugName` \- The debug name of the closure. Defaults to `luau.load(...)`.
  * `environment` \- A custom environment to load the chunk in. Setting a custom environment will deoptimize the chunk and forcefully disable codegen. Defaults to the global environment.
  * `injectGlobals` \- Whether or not to inject globals in the custom environment. Has no effect if no custom environment is provided. Defaults to `true`.
  * `codegenEnabled` \- Whether or not to enable codegen. Defaults to `false`.


# Example Scripts

These are a few short examples of things you can do using the `roblox` standard library.

## Make all parts anchored in a place file

Section titled “Make all parts anchored in a place file”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    -- Read the place file called myPlaceFile.rbxl into a DataModel variable called "game"

    -- This works exactly the same as in Roblox, except "game" does not exist by default.

    -- To access "game" you have to load it from a file!

    local file = fs.readFile("myPlaceFile.rbxl")

    local game = roblox.deserializePlace(file)

    local workspace = game:GetService("Workspace")

    -- Make all of the parts in the workspace anchored

    for _, descendant in workspace:GetDescendants() do

      if descendant:IsA("BasePart") then

        descendant.Anchored = true

      end

    end

    -- Save the DataModel (game) back to the file that we read it from

    file = roblox.serializePlace(game)

    fs.writeFile("myPlaceFile.rbxl", file)

## Save instances in a place as individual model files

Section titled “Save instances in a place as individual model files”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    -- Here we load a file just like in the first example

    local file = fs.readFile("myPlaceFile.rbxl")

    local game = roblox.deserializePlace(file)

    local workspace = game:GetService("Workspace")

    -- Make sure a directory exists to save our models in

    fs.writeDir("models")

    -- Then we save all of our instances in Workspace as model files, in our new directory

    -- Note that a model file can actually contain several instances at once, so we pass a table here

    for _, child in workspace:GetChildren() do

      file = roblox.serializeModel({ child })

      fs.writeFile("models/" .. child.Name, file)

    end

## Make a new place from scratch

Section titled “Make a new place from scratch”

    local fs = require("@lune/fs")

    local roblox = require("@lune/roblox")

    local Instance = roblox.Instance

    -- You can even create a new DataModel using Instance.new, which is not normally possible in Roblox

    -- This is normal - most instances that are not normally accessible in Roblox can be manipulated using Lune!

    local game = Instance.new("DataModel")

    local workspace = game:GetService("Workspace")

    -- Here we just make a bunch of models with parts in them for demonstration purposes

    for i = 1, 50 do

      local model = Instance.new("Model")

      model.Name = "Model #" .. tostring(i)

      model.Parent = workspace

      for j = 1, 4 do

        local part = Instance.new("Part")

        part.Name = "Part #" .. tostring(j)

        part.Parent = model

      end

    end

    -- As always, we have to save the DataModel (game) to a file when we're done

    local file = roblox.serializePlace(game)

    fs.writeFile("myPlaceWithLotsOfModels.rbxl", file)


# Process

Built-in functions for the current process & child processes

#### Example usage

Section titled “Example usage”

    local process = require("@lune/process")

    -- Getting the arguments passed to the Lune script

    for index, arg in process.args do

      print("Process argument #" .. tostring(index) .. ": " .. arg)

    end

    -- Getting the currently available environment variables

    local PORT: string? = process.env.PORT

    local HOME: string? = process.env.HOME

    for name, value in process.env do

      print("Environment variable " .. name .. " is set to " .. value)

    end

    -- Getting the current os and processor architecture

    print("Running " .. process.os .. " on " .. process.arch .. "!")

    -- Executing a command

    local result = process.exec("program", {

      "cli argument",

      "other cli argument"

    })

    if result.ok then

      print(result.stdout)

    else

      print(result.stderr)

    end

    -- Spawning a child process

    local child = process.create("program", {

      "cli argument",

      "other cli argument"

    })

    -- Writing to the child process' stdin

    child.stdin:write("Hello from Lune!")

    -- Reading from the child process' stdout

    local data = child.stdout:read()

    print(data)

## Properties

Section titled “Properties”

### os

Section titled “os”

`OS`

The current operating system being used.

Possible values:

  * `"linux"`
  * `"macos"`
  * `"windows"`

### arch

Section titled “arch”

`Arch`

The architecture of the processor currently being used.

Possible values:

  * `"x86_64"`
  * `"aarch64"`

### endianness

Section titled “endianness”

`Endianness`

The endianness of the processor currently being used.

Possible values:

  * `"big"`
  * `"little"`

### args

Section titled “args”

`{ string }`

The arguments given when running the Lune script.

### cwd

Section titled “cwd”

`string`

The current working directory in which the Lune script is running.

### env

Section titled “env”

`{ [string]: string? }`

Current environment variables for this process.

Setting a value on this table will set the corresponding environment variable.

## Functions

Section titled “Functions”

### exit

Section titled “exit”

Exits the currently running script as soon as possible with the given exit code.

Exit code 0 is treated as a successful exit, any other value is treated as an error.

Setting the exit code using this function will override any otherwise automatic exit code.

#### Parameters

Section titled “Parameters”

  * `code` The exit code to set

#### Returns

Section titled “Returns”

  * never

### create

Section titled “create”

Spawns a child process in the background that runs the program `program`, and immediately returns readers and writers to communicate with it.

In order to execute a command and wait for its output, see `process.exec`.

The second argument, `params`, can be passed as a list of string parameters to give to the program.

The third argument, `options`, can be passed as a dictionary of options to give to the child process. Refer to the documentation for `SpawnOptions` for specific option keys and their values.

#### Parameters

Section titled “Parameters”

  * `program` The program to Execute as a child process

  * `params` Additional parameters to pass to the program

  * `options` A dictionary of options for the child process

#### Returns

Section titled “Returns”

  * A dictionary with the readers and writers to communicate with the child process

### exec

Section titled “exec”

Executes a child process that will execute the command `program`, waiting for it to exit. Upon exit, it returns a dictionary that describes the final status and output of the child process.

In order to spawn a child process in the background, see `process.create`.

The second argument, `params`, can be passed as a list of string parameters to give to the program.

The third argument, `options`, can be passed as a dictionary of options to give to the child process. Refer to the documentation for `ExecOptions` for specific option keys and their values.

#### Parameters

Section titled “Parameters”

  * `program` The program to Execute as a child process

  * `params` Additional parameters to pass to the program

  * `options` A dictionary of options for the child process

#### Returns

Section titled “Returns”

  * A dictionary representing the result of the child process

## Types

Section titled “Types”

### ExecStdioKind

Section titled “ExecStdioKind”

Enum determining how to treat a standard input/output stream for `process.exec`.

Can be one of the following values:

  * `default` \- The default behavior, writing to the final result table only
  * `inherit` \- Inherit the stream from the parent process, writing to both the result table and the respective stream for the parent process
  * `forward` \- Forward the stream to the parent process, without writing to the result table, only respective stream for the parent process
  * `none` \- Do not create any input/output stream

### ExecStdioOptions

Section titled “ExecStdioOptions”

A dictionary of stdio-specific options for `process.exec`, with the following available values:

  * `stdin` \- A buffer or string to write to the stdin of the process
  * `stdout` \- How to treat the stdout stream from the child process - see `ExecStdioKind` for more info
  * `stderr` \- How to treat the stderr stream from the child process - see `ExecStdioKind` for more info

### ExecOptions

Section titled “ExecOptions”

A dictionary of options for `process.exec`, with the following available values:

  * `cwd` \- The current working directory for the process
  * `env` \- Extra environment variables to give to the process
  * `shell` \- Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell
  * `stdio` \- How to treat output and error streams from the child process - see `StdioKind` and `StdioOptions` for more info

### CreateOptions

Section titled “CreateOptions”

A dictionary of options for `process.create`, with the following available values:

  * `cwd` \- The current working directory for the process
  * `env` \- Extra environment variables to give to the process
  * `shell` \- Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell

### ChildProcess

Section titled “ChildProcess”

Result type for child processes in `process.create`.

This is a dictionary containing the following values:

  * `stdin` \- A writer to write to the child process’ stdin - see `ChildProcessWriter` for more info
  * `stdout` \- A reader to read from the child process’ stdout - see `ChildProcessReader` for more info
  * `stderr` \- A reader to read from the child process’ stderr - see `ChildProcessReader` for more info
  * `kill` \- A method that kills the child process
  * `status` \- A method that yields and returns the exit status of the child process

### ExecResult

Section titled “ExecResult”

Result type for child processes in `process.exec`.

This is a dictionary containing the following values:

  * `ok` \- If the child process exited successfully or not, meaning the exit code was zero or not set
  * `code` \- The exit code set by the child process, or 0 if one was not set
  * `stdout` \- The full contents written to stdout by the child process, or an empty string if nothing was written
  * `stderr` \- The full contents written to stderr by the child process, or an empty string if nothing was written

# ChildProcessReader

Section titled “ChildProcessReader”

A reader class to read data from a child process’ streams in realtime.

## Functions

Section titled “Functions”

### read

Section titled “read”

Reads a chunk of data up to the specified length, or a default of 1KB at a time.

Returns nil if there is no more data to read.

This function may yield until there is new data to read from reader, if all data till present has already been read, and the process has not exited.

#### Parameters

Section titled “Parameters”

  * `chunkSize` number?

#### Returns

Section titled “Returns”

  * The string containing the data read from the reader

### readToEnd

Section titled “readToEnd”

Reads all the data currently present in the reader as a string. This function will yield until the process exits.

#### Returns

Section titled “Returns”

  * The string containing the data read from the reader

# ChildProcessWriter

Section titled “ChildProcessWriter”

A writer class to write data to a child process’ streams in realtime.

## Functions

Section titled “Functions”

### write

Section titled “write”

Writes a buffer or string of data to the writer.

#### Parameters

Section titled “Parameters”

  * `data` The data to write to the writer

### close

Section titled “close”

Closes the underlying I/O stream for the writer.


# Net

Built-in library for network access

#### Example usage

Section titled “Example usage”

    local net = require("@lune/net")

    local serde = require("@lune/serde")

    -- Sending a web request

    local response = net.request("https://www.google.com")

    print(response.ok)

    print(response.statusCode, response.statusMessage)

    print(response.headers)

    -- Using a JSON web API

    local response = net.request({

      url = "https://dummyjson.com/products/add",

      method = "POST",

      headers = { ["Content-Type"] = "application/json" },

      body = serde.encode("json", {

        title = "Cool Pencil",

      })

    })

    local product = serde.decode("json", response.body)

    print(product.id, "-", product.title)

    -- Starting up an http server

    net.serve(8080, function(request)

      return {

        status = 200,

        body = "Echo:\n" .. request.body,

    end)

    -- Writing to a plain TCP stream

    local conn = net.tcp.connect("example.com", 80)

    conn:write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")

## Functions

Section titled “Functions”

### request

Section titled “request”

Sends an HTTP request using the given url and / or parameters, and returns a dictionary that describes the response received.

Only throws an error if a miscellaneous network or I/O error occurs, never for unsuccessful status codes.

#### Parameters

Section titled “Parameters”

  * `config` The URL or request config to use

#### Returns

Section titled “Returns”

  * A dictionary representing the response for the request

### socket

Section titled “socket”

Connects to a web socket at the given URL.

Throws an error if the server at the given URL does not support web sockets, or if a miscellaneous network or I/O error occurs.

#### Parameters

Section titled “Parameters”

  * `url` The URL to connect to

#### Returns

Section titled “Returns”

  * A web socket handle

### serve

Section titled “serve”

Creates an HTTP server that listens on the given `port`.

This will **_not_ ** block and will keep listening for requests on the given `port` until the `stop` function on the returned `ServeHandle` has been called.

#### Parameters

Section titled “Parameters”

  * `port` The port to use for the server

  * `handlerOrConfig` The handler function or config to use for the server

#### Returns

Section titled “Returns”

  * ServeHandle

### urlEncode

Section titled “urlEncode”

Encodes the given string using URL encoding.

#### Parameters

Section titled “Parameters”

  * `s` The string to encode

  * `binary` If the string should be treated as binary data and/or is not valid utf-8. Defaults to false

#### Returns

Section titled “Returns”

  * The encoded string

### urlDecode

Section titled “urlDecode”

Decodes the given string using URL decoding.

#### Parameters

Section titled “Parameters”

  * `s` The string to decode

  * `binary` If the string should be treated as binary data and/or is not valid utf-8. Defaults to false

#### Returns

Section titled “Returns”

  * The decoded string

## Types

Section titled “Types”

### FetchParamsOptions

Section titled “FetchParamsOptions”

Extra options for `FetchParams`.

This is a dictionary that may contain one or more of the following values:

  * `decompress` \- If the request body should be automatically decompressed when possible. Defaults to `true`

### FetchParams

Section titled “FetchParams”

Parameters for sending network requests with `net.request`.

This is a dictionary that may contain one or more of the following values:

  * `url` \- The URL to send a request to. This is always required
  * `method` \- The HTTP method verb, such as `"GET"`, `"POST"`, `"PATCH"`, `"PUT"`, or `"DELETE"`. Defaults to `"GET"`
  * `body` \- The request body
  * `query` \- A table of key-value pairs representing query parameters in the request path
  * `headers` \- A table of key-value pairs representing headers
  * `options` \- Extra options for things such as automatic decompression of response bodies

### FetchResponse

Section titled “FetchResponse”

Response type for sending network requests with `net.request`.

This is a dictionary containing the following values:

  * `ok` \- If the status code is a canonical success status code, meaning within the range 200 -> 299
  * `statusCode` \- The status code returned for the request
  * `statusMessage` \- The canonical status message for the returned status code, such as `"Not Found"` for status code 404
  * `headers` \- A table of key-value pairs representing headers
  * `body` \- The request body, or an empty string if one was not given

### ServeRequest

Section titled “ServeRequest”

Data type for requests in `net.serve`.

This is a dictionary containing the following values:

  * `path` \- The path being requested, relative to the root. Will be `/` if not specified
  * `query` \- A table of key-value pairs representing query parameters in the request path
  * `method` \- The HTTP method verb, such as `"GET"`, `"POST"`, `"PATCH"`, `"PUT"`, or `"DELETE"`. Will always be uppercase
  * `headers` \- A table of key-value pairs representing headers
  * `body` \- The request body, or an empty string if one was not given

### ServeResponse

Section titled “ServeResponse”

Response type for requests in `net.serve`.

This is a dictionary that may contain one or more of the following values:

  * `status` \- The status code for the request, in the range `100` -> `599`
  * `headers` \- A table of key-value pairs representing headers
  * `body` \- The response body

### ServeConfig

Section titled “ServeConfig”

Configuration for `net.serve`.

This may contain one of or more of the following values:

  * `address` for setting the IP address to serve from. Defaults to the loopback interface (`http://localhost`).
  * `handleRequest` for handling normal http requests, equivalent to just passing a function to `net.serve`
  * `handleWebSocket` for handling web socket requests, which will receive a `WebSocket` object as its first and only parameter

When setting `address`, the `handleRequest` callback must also be defined.

#### Example Usage

Section titled “Example Usage”

    net.serve(8080, {

      address = "http://0.0.0.0",

      handleRequest = function(request)

        return {

          status = 200,

          body = "Echo:\n" .. request.body,

      end

    })

### ServeHandle

Section titled “ServeHandle”

A handle to a currently running web server, containing a single `stop` function to gracefully shut down the web server.

### WebSocket

Section titled “WebSocket”

A reference to a web socket connection.

The web socket may be in either an “open” or a “closed” state, changing its current behavior.

When open:

  * Any function on the socket such as `send`, `next` or `close` can be called without erroring
  * `next` can be called to yield until the next message is received or the socket becomes closed

When closed:

  * `next` will no longer return any message(s) and instead instantly return nil
  * `send` will throw an error stating that the socket has been closed

Once the websocket has been closed, `closeCode` will no longer be nil, and will be populated with a close code according to the [WebSocket specification](https://www.iana.org/assignments/websocket/websocket.xhtml). This will be an integer between 1000 and 4999, where 1000 is the canonical code for normal, error-free closure.

### TcpConfig

Section titled “TcpConfig”

Configuration options for a TCP stream.

#### Example Usage

Section titled “Example Usage”

    -- Plain TCP connection

    local stream = net.tcp.connect("example.com", 80)

    -- TLS connection (shorthand)

    local stream = net.tcp.connect("example.com", 443, true)

    -- TLS connection (explicit config)

    local stream = net.tcp.connect("example.com", 443, { tls = true })

    -- Connection with custom TTL

    local stream = net.tcp.connect("192.168.1.100", 8080, {

      tls = false,

      ttl = 128

    })

### TcpStream

Section titled “TcpStream”

A plain TCP stream, which may also be backed by a TLS connection.

#### Example Usage

Section titled “Example Usage”

    local net = require("@lune/net")

    local conn = net.tcp.connect("example.com", 80)

    conn:write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")

    local response = conn:read()

    print(response)

    conn:close()

# Tcp

Section titled “Tcp”

TCP primitives for the `net` library

Provides low-level TCP socket functionality for creating custom network protocols or communicating with services that don’t use HTTP - for all HTTP usage, please use the `request` and `serve` HTTP functions instead.

## Functions

Section titled “Functions”

### connect

Section titled “connect”

Connects to the given host and port, returning a `TcpStream`.

For additional details, see the documentation for the `TcpConfig` and `TcpStream` types.

Will throw an error if the connection fails.

#### Parameters

Section titled “Parameters”

  * `host` The host to connect to, either a DNS name or IP address

  * `port` The port to connect to

  * `config` The optional configuration to use for the stream

#### Returns

Section titled “Returns”

  * A connected TcpStream ready for reading and writing


# Arguments

When you run a Lune script, you can pass information to it directly from the command line. These are called arguments, and they’re incredibly useful for making your scripts flexible and reusable.

## Passing Arguments

Section titled “Passing Arguments”

Passing arguments when running a script is dead simple. Here’s how:

Terminal

    lune run script-name arg1 arg2 "argument three"

## Using Arguments

Section titled “Using Arguments”

Your script can then access these arguments through the `process` standard library:

    local process = require("@lune/process")

    print(process.args)

    --> { "arg1", "arg2", "argument three" }

The arguments will always be an array (table) of strings, in the same order you provided them.

## A Practical Example

Section titled “A Practical Example”

Let’s create a script that greets someone by name:

greet.luau

    local process = require("@lune/process")

    if #process.args == 0 then

      print("Usage: lune run greet <name>")

      print("Example: lune run greet Alice")

    else

      local name = process.args[1]

      print(`Hello, {name}! Welcome to Lune.`)

    end

Now you can run it with different names:

Terminal

    lune run greet Alice

    --> Hello, Alice! Welcome to Lune.

    lune run greet "John Doe"

    --> Hello, John Doe! Welcome to Lune.

## Combining Arguments with User Input

Section titled “Combining Arguments with User Input”

Here’s a clever pattern - use arguments when provided, but fall back to prompting the user if they’re missing:

smart-greet.luau

    local process = require("@lune/process")

    local stdio = require("@lune/stdio")

    local name

    if #process.args > 0 then

      name = process.args[1]

    else

      name = stdio.prompt("text", "What's your name?")

    end

    print(`Hello, {name}!`)

This script works both ways - with arguments or interactively!

## What’s Next?

Section titled “What’s Next?”

Now that you can handle user input and arguments, let’s explore one of Lune’s most powerful features - the standard library for networking. Head over to [Networking](https://lune-org.github.io/docs/the-book/4-arguments/5-networking) to learn more.


# Networking

Now we’re getting to the really fun stuff! The `net` library lets your scripts talk to the internet - whether that’s fetching data from websites, calling APIs, or even creating your own web servers.

## Making Web Requests

Section titled “Making Web Requests”

Let’s start with something simple - fetching a web page:

simple-request.luau

    local net = require("@lune/net")

    local response = net.request("https://www.example.com")

    if response.ok then

      print(`Success! Got {#response.body} bytes`)

      print(`Status: {response.statusCode} {response.statusMessage}`)

    else

      print(`Request failed: {response.statusCode}`)

    end

When you make a request, you get back a response object with everything you need - the status code, headers, body content, and an `ok` field that tells you if things went well.

## Working with APIs

Section titled “Working with APIs”

Most modern web services use APIs that speak JSON. Here’s how you can work with them:

github-api.luau

    local net = require("@lune/net")

    local serde = require("@lune/serde")

    -- Let's search GitHub for popular Luau projects

    local response = net.request({

      url = "https://api.github.com/search/repositories",

      query = {

        q = "language:luau",

        sort = "stars",

        per_page = "3"

    })

    if response.ok then

      local results = serde.decode("json", response.body)

      print(`Found {results.total_count} Luau repositories!\n`)

      for _, repo in results.items do

        print(`⭐ {repo.stargazers_count} - {repo.full_name}`)

        print(`   {repo.description}\n`)

      end

    end

When you need to send data to an API, you’ll typically use methods other than GET, such as POST or PATCH:

posting-stuff.luau

    local response = net.request({

      url = "https://api.example.com/data",

      method = "POST",

      headers = { ["Content-Type"] = "application/json" },

      body = serde.encode("json", {

        name = "My Lune Script",

        version = "1.0.0"

      })

    })

The `serde` library handles all the JSON encoding and decoding for you, so you can work with regular Lua tables and other datatypes.

## Running a Web Server

Section titled “Running a Web Server”

Sometimes you don’t want to make requests - you want to receive them. Creating a web server with Lune is surprisingly easy:

my-server.luau

    local net = require("@lune/net")

    local visitCount = 0

    net.serve(8080, function(request)

      visitCount += 1

      print(`[{request.method}] {request.path} - Visit #{visitCount}`)

      if request.path == "/" then

        return {

          status = 200,

          headers = { ["Content-Type"] = "text/html" },

          body = `
Hello, visitor #{visitCount}!

Try visiting /api
`

      elseif request.path == "/api" then

        return {

          status = 200,

          headers = { ["Content-Type"] = "text/plain" },

          body = `You are visitor number {visitCount}`

      else

        return { status = 404, body = "Page not found" }

      end

    end)

    print("Server running at http://localhost:8080")

    print("Press Ctrl+C to stop")

Your server can handle different routes, check request methods and headers, parse request bodies - everything you need for building real web applications.

## Beyond HTTP

Section titled “Beyond HTTP”

The `net` library has even more tricks up its sleeve. It can handle WebSockets for real-time communication, raw TCP connections for custom protocols, and more.

### WebSockets

Section titled “WebSockets”

WebSockets are perfect when you need real-time, two-way communication. Lune makes these very easy to use as well:

websocket-client.luau

    local net = require("@lune/net")

    -- Connect to a WebSocket echo server

    local socket = net.socket("wss://echo.websocket.org")

    socket:send("Hello from Lune!")

    -- Wait for the echo

    local reply = socket:next()

    print(`Server echoed: {reply}`)

    socket:close()

Extra: WebSocket Echo Server

Here’s a fun example that combines HTTP and WebSocket handling to create an interactive echo server:

echo-server.luau

    local net = require("@lune/net")

    net.serve(8080, {

      handleRequest = function(request)

        return {

          status = 200,

          headers = { ["Content-Type"] = "text/html" },

          body = [[

WebSocket Echo Test

            <input id="message" placeholder="Type a message">

            <button onclick="send()">Send</button>

      end,

      handleWebSocket = function(socket)

        print("WebSocket connected!")

        for message in socket do

          print(`Received: {message}`)

          socket:send(`Echo: {message}`)

        end

        print("WebSocket disconnected")

      end

    })

    print("Echo server running at http://localhost:8080")

Try running this and opening <http://localhost:8080> in your browser. You’ll have a working chat interface!

Advanced: TCP Connections

For those times when you need to speak a protocol that isn’t HTTP, you can use raw TCP connections, optionally backed by TLS:

raw-tcp.luau

    local net = require("@lune/net")

    -- Connect to example.com on port 80 (HTTP)

    local conn = net.tcp.connect("example.com", 80)

    -- Send a raw HTTP request

    conn:write("GET / HTTP/1.1\r\n")

    conn:write("Host: example.com\r\n")

    conn:write("Connection: close\r\n")

    conn:write("\r\n")

    -- Read the response

    local response = conn:read()

    print("Response received:")

    print(response)

    -- Keep reading until the server closes the connection

    while true do

      local chunk = conn:read()

      if chunk == nil or #chunk == 0 then

        break

      end

      print(chunk)

    end

    conn:close()

Here’s how you can use TLS for secure connections - add a simple flag argument for enabling it:

    -- Connect with TLS enabled

    local secure = net.tcp.connect("example.com", 443, true)

This gives you the power to implement any TCP-based protocol - SMTP, FTP, custom game protocols, you name it. Lune does not currently provide built-in support for databases, but with TCP connections you can use existing clients built for other runtimes without much extra effort.

## What’s Next?

Section titled “What’s Next?”

With all this network power at your fingertips, you’ll probably want to save some of that data you’re fetching. Let’s explore how to work with files and directories in [Working with Files](https://lune-org.github.io/docs/the-book/5-networking/6-working-with-files).


# ByteNet

## Simple, buffer-based networking.

[GitHub](https://github.com/ffrostfall/ByteNet) | [Documentation](https://ffrostfall.github.io/ByteNet/)

ByteNet is an networking library which takes your Luau data, and serializes it into buffers. On the other end, ByteNet deserializes your data, and then feeds it back to your Luau code. You don't need to worry about type validation, optimization, packet structure, etc. ByteNet does all the hard parts for you! Strictly typed with an incredibly basic API that explains itself, ByteNet makes networking simple, easy, and quick. There's very few concepts you need to grasp in order to use ByteNet; it has an incredibly minimalistic & simplistic, yet powerful API.

## Installation

You can install ByteNet on Wally, or through the latest release's `.rbxm` file.

## Performance

ByteNet performs incredibly well compared to non-buffer based libraries like BridgeNet2. This is because ByteNet has a **custom serializer** that takes your Luau data and transforms it into a buffer, sending that and deserializing it on the other side.

## Further contact

You can contact me directly under the ByteNet thread in the [Roblox OSS Server](https://discord.gg/5KjV64PA3d).

Further documentation [here](https://ffrostfall.github.io/ByteNet/).

## License

This project is under the MIT license! so, it's open source


# simple buffer-based networking

In ByteNet, you don't need to worry about type validation, optimization, packet structure, etc. ByteNet does all the hard parts for you! Strictly typed with an incredibly basic API that explains itself, ByteNet makes networking simple, easy, and quick.

[Getting started](https://ffrostfall.github.io/ByteNet/api/functions/definePacket) [Download](https://github.com/ffrostflame/ByteNet/releases/latest)

_practical example of a packet's definition_

packets.luau

    local ByteNet = require(path.to.ByteNet)

    return ByteNet.defineNamespace("example", function()
        return {
            vectorStringMap = ByteNet.definePacket({
                value = ByteNet.dataTypes.map(ByteNet.dataTypes.vec3(), ByteNet.dataTypes.string())
            })
    end)

_example of sending a packet to all clients_

server.luau

    local packets = require(path.to.packets)

    packets.myPacket.sendToAll({
        [Vector3.new(1, 1, 1)] = "10x less bandwidth than a remote!",
        [Vector3.new(1, 2, 1)] = "oh wait, its way more than 10x less.",

        [Vector3.new(2, 1, 1)] = "depending on how you use your data,",
        [Vector3.new(1, 1, 2)] = "it could be 100x less!",
    })

# Not enough? here's more

## No more RemoteEvents

ByteNet handles all instances for you; you don't need to worry about reliability type, parenting, etc

## - As low-level as you can get

ByteNet lets you directly read/write data types such as uint32, buffers, in a way where the keys are abstracted away. Reap the benefits of a structured, strictly typed library, while retaining the benefits of hardcore optimization.

Back to top


# Defining a packet

## what even is a packet in ByteNet anyway?

Packets are the structured dictionaries you use to define the "format" your data is going to be sent in. These are named packets as you 'send' packets through network, and packets have their contents usually formatted in a specific way because, well they have to be.

ByteNet's purpose as a library is to provide a way to structure your data, and send that structure in a hyper-optimized way.

## Okay, where do I start?

Enough of how it works, let's start off with making a basic packet. We will also need to create a namespace:

packets.luau

    local ByteNet = require(path.to.bytenet)

    return ByteNet.defineNamespace("messaging", function()
        return {
            printSomething = ByteNet.definePacket({
            -- This value field is very important!
                value = ByteNet.struct({
                    message = ByteNet.string,
                })
            })
    end)

## Sending

On the server, there are 3 methods of sending packets to players. It's important to note that when a player should be specified, it's the _second_ parameter given, not the first. This is an intentional design choice.

  * `sendToAll`
  * `sendToAllExcept`
  * `sendTo`

On the client, there is only one, because you can only send data to the server:

  * `send`

Any data passed through these functions _must_ obey your structure created in `definePacket`, and if you have strict typing on, an error will be raised if the types do not match.

_code examples_

client.luau

    -- Sending to server
    packets.myPacket.send({
        message = "Hello, world!"
    })

server.luau

    -- Sending to all players
    packets.myPacket.sendToAll({
        message = "Hello, players!"
    })

    -- Sending to an individual player
    local someArbitraryPlayer = Players.You

    packets.myPacket.sendTo({
        message = "Hello, random player!"
    }, someArbitraryPlayer)

    -- Sending to all except a certain player
    local someArbitraryPlayer = Players.You

    packets.myPacket.sendToAllExcept({
        message = "Hello, everyone except one person!"
    }, someArbitraryPlayer)

## Receiving

You can use the `listen` method to listen for when a packet is received.

_code examples_

server.luau

    packets.myPacket.listen(function(data, player)
        print(`{player.UserId} says { data.message }`)
    end)

client.luau

    packets.myPacket.listen(function(data)
        print(`server says { data.message }`)
    end)

## Configuration (Reliability types)

Currently, the only config accessible right now is reliability types. This will change as time goes on, however right now, you can switch reliability types by defining `reliabilityType` to be `"reliable"` or `"unreliable"`.

Back to top


# Available special types

Special types are how complex packet types are made. They can take in nearly any type, including themselves, and most importantly they are dynamic. This means you can have an array of any length, a map of any key type and any value type, and an optional value of any type.

Special types always take _parameters_. You have to call them: `ByteNet.<type name>(<any primitive type>)`.

Danger

  * Using these types incurs 1-2 bytes of overhead due to the dynamic nature.
  * They take drastically more time to parse
  * They are heavier on memory usage, as a new closure is created each time. You will never have to worry about this unless you have dozens of packets, though.

## Structs

Structs let you send structured data in the form of dictionaries. The fields are optimized away, so they don't take any bandwidth. Structs are how you'll be sending most of your data likely: they are the best for organization, and they let you do really interesting things. For example:

chunkPackets.luau

    return ByteNet.defineNamespace("chunks", function()
        local chunkData = ByteNet.struct({
            biome = ByteNet.uint8,
            seed = ByteNet.int32,
        })

        return {
            sendChunks = ByteNet.definePacket({
                value = ByteNet.array(chunkData)
            })
    end)

## Optionals

Optional types are a cool name for the concept of "This doesn't have to exist". It's good for optional parameters: for example if some invoked function were to fail, you might want to send back a blank copy to indicate that something is wrong.

packets.luau

    return ByteNet.defineNamespace("chunks", function()
        return {
            myCoolPacket = ByteNet.definePacket({
                value = ByteNet.struct({
                    -- An "optional" type takes in a parameter.
                    -- This can be anything! You can even have optional arrays.
                    helloWorldString = ByteNet.optional(ByteNet.string)

                    -- This works!
                    eachCharacter = ByteNet.optional(ByteNet.array(ByteNet.string))
                }),
            })
    end)

You really don't have to think about using optional types. You just send it!

server.luau

    local packets = require(path.to.packets)

    local randomlyStringOrNil =
        if math.random(1, 2) == 1 then "Hello, world!" else nil

    packets.myCoolPacket.sendToAll({
        helloWorldString = randomlyAppearingString,

        -- Note that even if we don't put the "eachCharacter" field here,
        -- it won't error. This is because it's optional!
    })

## Arrays

Arrays are fairly self explanatory. They're just plain old arrays. However, it's important to note that mixed tables have **undefined** behavior when passed as an array. This means things might be missing when they come out on the other side!

There is a 2 byte overhead to sending an array in ByteNet. This is because these 2 bytes are an unsigned 16-bit integer, which stores the array length. As a side effect, arrays sent through ByteNet have a max length of **2^16**, which is equal to **65,536**. It's likely that in the future, you will be able to reduce the overhead to 1 byte through configuration, in turn reducing the max length of the array.

packets.luau

    return ByteNet.defineNamespace("example", function()
        return {
            myCoolPacket = ByteNet.definePacket({
                value = ByteNet.array(ByteNet.bool),
            })

server.luau

    local packets = require(path.to.packets)

    packets.myCoolPacket.sendToAll({
        -- Important to note that mixed arrays/arrays with holes
        -- shouldn't be sent through.
        true, false, true
    })

## Maps

Maps are by far the most powerful "special" type in ByteNet. They let you send, what's most commonly referred to as a dictionary, through ByteNet. However it's important to keep in mind two things: the type of the key (or index), and the type of the value, cannot change.

Like arrays, maps have a 2-byte overhead to store the length of the map. This is done by iterating over the map using [generic iteration](https://devforum.roblox.com/t/luau-recap-may-2022/1818869) and increasing a variable by 1 for every key-value pair. This, once again, means that there is a **2^16** (which equals to **65,536**) cap to the number of elements in the map.

packets.luau

    return ByteNet.defineNamespace("example", function()
        return {
            people = ByteNet.definePacket({
                -- [name] = age
                value = ByteNet.map(ByteNet.string, ByteNet.uint8)
            })
    end)

server.luau

    local packets = require(path.to.packets)

    packets.myCoolPacket.sendToAll({
        john = 21,
        jane = 24,
        dave = 26,
        you = 162,
    })

Back to top


# Available primitive types

ByteNet provides a large amount of "primitive" types for you to build more complex types that suit your game. Since primitive types don't need any parameters, you can just access them like the following: `ByteNet.<typename>`. For building more complex data structures, go look at the [Specials page.](https://ffrostfall.github.io/ByteNet/api/dataTypes/Specials)

## Supported general types

  * `string`: String
  * `buff`: Buffer
  * `bool`: Boolean
  * `nothing`: Nothing
  * `unknown`: Any type

## Supported number types

  * `uint8`: Unsigned 8-bit integer
  * `uint16`: Unsigned 16-bit integer
  * `uint32`: Unsigned 32-bit integer
  * `int8`: Signed 8-bit integer
  * `int16`: Signed 16-bit integer
  * `int32`: Signed 32-bit integer
  * `float32`: Standard 32-bit float
  * `float64`: Standard 64-bit float

## Supported Roblox types

  * `cframe`: CoordinateFrame
  * `vec2`: Vector2
  * `vec3`: Vector3
  * `inst`: Instance

Back to top


An IDL compiler written in Luau for ROBLOX buffer networking

# Performance
Blink aims to generate the most performant and bandwidth-efficient code for your specific experience, but what does this mean?

It means lower bandwidth usage directly resulting in **lower ping\*** experienced by players and secondly, it means **lower CPU usage** compared to more generalized networking solutions.

*\* In comparison to standard ROBLOX networking, this may not always be the case but should never result in increased ping times.*

Benchmarks are available here [here](./benchmark/Benchmarks.md).

# Security
Blink does two things to combat bad actors:
1. Data sent by clients will be **validated** on the receiving side before  reaching any critical game code.
2. As a result of the compression done by Blink it becomes **significantly harder** to snoop on your game's network traffic. Long gone are the days of skids using RemoteSpy to snoop on your game's traffic.

# Get Started
Head over to the [installation](https://1axen.github.io/blink/getting-started/1-installation) page to get started with Blink.

# Credits
Credits to [Zap](https://zap.redblox.dev/) for the range and array syntax
Credits to [ArvidSilverlock](https://github.com/ArvidSilverlock) for the float16 implementation
Studio plugin auto completion icons are sourced from [Microsoft](https://github.com/microsoft/vscode-icons) and are under the [CC BY 4.0](https://github.com/microsoft/vscode-icons/blob/main/LICENSE) license.
Speed icons created by alkhalifi design - Flaticon


📦

## Buffer Packing

Zap packs data into buffers with no overhead. The same data can be sent using a fraction of the bandwidth.


# Installation

## With Aftman

## From GitHub Releases

You can get the latest version from [GitHub Releases](https://github.com/red-blox/zap/releases/).


# Generating Luau Code

## Via the CLI

After [installing Zap](https://zap.redblox.dev/intro/getting-started.html), you can run the CLI with `zap [path to config file]`.

The CLI assumes an output folder of `network` unless the [server_output and client_output](https://zap.redblox.dev/config/options.html#server-output-client-output) options are specified in the config file.

## Via the playground

Head to the [playground](https://zap.redblox.dev/playground.html), input your Zap config file and paste the outputs into your IDE.


# Tooling Integration

Zap supports the ability to output a Lua file that can deseralise compressed data so that it can be read by plugins and/or scripts such as [PacketProfiler](https://github.com/Pyseph/PacketProfiler/).

Zap and PacketProfiler have created a common format for this interfacing as detailed below:

## Tooling Format - Parameters

To deseralise this data, the function generated by Zap has the following parameters:

  * The remote instance itself (`RemoteEvent | UnreliableRemoteEvent`)
  * ALL arguments sent through the remote

An example is below:

DANGER

It is not intended behavior for you to run side effects if you deserialise the data yourself.

## Tooling Format - Returns

The function then returns an array in this format:

Lua:

TS:

If the [`tooling_show_internal_data`](https://zap.redblox.dev/config/options.html#tooling-show-internal-data) argument is `true`, then the first argument is of the type (although this type will respect your casing option):

Lua:

TS:

And then the next argument in the array is the data deserialised from the event itself (if it exists).


# Using Your Generated Code

After [generating code](https://zap.redblox.dev/usage/generation.html), Zap provides two output files: the client API and the server API. Detailed documentation for both is provided below.

INFO

This page will assume you're using the default casing value of `PascalCase`.

Learn more about casing options [here](https://zap.redblox.dev/config/options.html#casing).

## Event API

In this section, we use the following Zap file as an example.

Display in Playground

### Listening to Events

Listening to events works the same on both the server and client. It is assumed that `Zap` is a properly defined reference to the generated API.

If your event's [call field](https://zap.redblox.dev/config/events.html#call) is `SingleAsync` or `SingleSync` you can assign only one listener the `SetCallback` function.

lua

    -- only server listeners are given the player argument
    Zap.AnotherEvent.SetCallback(function(Player, Foo, Bar)
        -- Do something with the player and data
    end)

If your event's [call field](https://zap.redblox.dev/config/events.html#call) is `ManyAsync` or `ManySync` you can assign multiple listeners using the `On` function.

lua

    local Disconnect = Zap.MyEvent.On(function(Options)
        -- Do something with the data
    end)

    -- Disconnect the listener after 10 seconds
    task.delay(10, Disconnect)

As shown above, the `On` function for `Many` style events returns a `Disconnect` function, which can be used to remove the listener.

DANGER

Remember that synchronous event callbacks must not yield or error:

  * If a sync callback yields, it will cause undefined and game-breaking behavior.
  * If a sync callback errors, it will drop the packet.

Use `Sync` events only when performance is critical.

## Client Event API

The client has a single function for firing events, `Fire`, which takes the event's data as its arguments.

lua

    Zap.AnotherEvent.Fire(true, 32)

## Server Event API

The server has many functions for firing events, each with their own use case.

TIP

`FireAll`, `FireExcept`, `FireList`, and `FireSet` serialize the event's data only once, making them more efficient than firing the event individually to each player.

Use these functions when sending the same data to multiple players.

### Fire

The `Fire` function takes a player and the event's data as its arguments.

lua

    Zap.MyEvent.Fire(Player, {
        foo = "baz",
        bar = 1,
    })

### FireAll

The `FireAll` function takes the event's data as its arguments. It will fire the event to all players.

INFO

`FireAll` can be disabled using the [disable_fire_all](https://zap.redblox.dev/config/options.html#disable-fire-all) option. Attempting to call `FireAll` when `disable_fire_all = true` will produce an error.

lua

    Zap.MyEvent.FireAll({
        foo = "baz",
        bar = 1,
    })

### FireExcept

The `FireExcept` function takes a player and the event's data as its arguments. It will fire the event to all players except the specified player.

lua

    Zap.MyEvent.FireExcept(Player, {
        foo = "baz",
        bar = 1,
    })

### FireList

The `FireList` function takes a list of players and the event's data as its arguments. It will fire the event to all players in the list.

lua

    Zap.MyEvent.FireList({ Player1, Player2 }, {
        foo = "baz",
        bar = 1,
    })

### FireSet

The `FireSet` function takes a set of players and the event's data as its arguments. It will fire the event to all players in the set.

TIP

A [set](https://zap.redblox.dev/config/types.html#sets) is a table where the keys are of a certain datatype and the values are always `True`.

lua

    Zap.MyEvent.FireSet({
        [Player1] = true,
        [Player2] = true,
    }, {
        foo = "baz",
        bar = 1,
    })

## Function API

In this section, we use the following Zap file as an example.

Display in Playground

## Client Function API

### Call

The client has a single function for invoking the server, `Call`, which takes the function's data as its arguments.

lua

    local score = Zap.GetScore.Call("round_ipsum-lorem", "LowScore")
    print(score)

## Server Function API

### SetCallback

The server has a single function for responding to client invocations, `SetCallback`. This is similar to `Zap.MyEvent.SetCallback`, but it instead has a return as defined by the Zap config.

lua

    local function handleRequest(Player: Player, roundId: string, category: "HighScore" | "LowScore" | "AverageScore"): number
        -- Do something with the data.
        return 0 -- We must return a u16 according to our Zap config.
    end

    Zap.GetScore.SetCallback(handleRequest)


# Options

Options are placed at the beginning of Zap config files and allow you to configure the way Zap generates code.

## `server_output` & `client_output`

These two options allow you to configure where Zap will output generated code. If you're not using the CLI these options can be ignored.

The paths are relative to the configuration file and should point to a lua(u) file.

### Default

Display in Playground

### Example

Display in Playground

## `types_output` [`0.6.18+`]

Configures where Luau types will be output.

The path is relative to the configuration file and should point to a lua(u) file.

### Example

Display in Playground

## `call_default` [`0.6.18+`]

The default `call` field that will be used for events. See [call](https://zap.redblox.dev/config/events.html#call) for possible options.

### Example

Display in Playground

Display in Playground

### Default

Requires an explicit `call` field defined on every event.

## `remote_scope`

This option changes the name of the remotes generated by Zap.

### Default

Display in Playground

The generated remotes will be `ZAP_RELIABLE` and `ZAP_UNRELIABLE` respectively.

### Example

Display in Playground

The generated remotes will change to be `PACKAGE_NAME_RELIABLE` and `PACKAGE_NAME_UNRELIABLE` respectively.

## `remote_folder`

This option changes the name folder that Zap's remotes are placed inside of ReplicatedStorage.

### Default

Display in Playground

The generated remotes will be in `ReplicatedStorage -> ZAP`.

### Example

Display in Playground

The generated remotes be in `ReplicatedStorage -> CHARACTER`

## `casing`

This option changes the casing of the API generated by Zap.

Using the `FireAll` function as an example:

Casing| Example
`PascalCase`| `FireAll`
`camelCase`| `fireAll`
`snake_case`| `fire_all`

INFO

This option does not change the casing of your event or type names. It only changes the casing of generated API functions.

### Default

`"PascalCase"`

### Options

  * `"camelCase"`
  * `"PascalCase"`
  * `"snake_case"`

### Example

Display in Playground

## `write_checks`

This option determines if Zap should check types when writing data to the network. This is useful for development and debugging, but can see some performance hits, and should be disabled in production.

DANGER

Zap only checks types that cannot be statically checked by Luau or TypeScript.

For example, Zap will not check if a `string (20)` is a string, but it will check that the string is 20 characters long.

### Default

`true`

### Options

  * `true`
  * `false`

### Example

Display in Playground

## `typescript`

This option determines if Zap should generate TypeScript definition files alongside generated Luau code.

When enabled, Zap will generate a `.d.ts` file for the server and client with the same paths as the generated Luau server and client.

### Default

`false`

### Options

  * `true`
  * `false`

### Example

Display in Playground

## `manual_event_loop`

This option determines if Zap automatically sends reliable events and functions each Heartbeat.

When enabled, the `SendEvents` function exported from the client and server modules that must be called manually.

This is useful when you can easily run `SendEvents` after all events have been fired each frame.

DANGER

At the time of writing (January 2024), Roblox has an issue where firing remotes at too high of a rate (above 60 hz) can cause the server to have incredibly high network response times.

**This causes servers to essentially crash, and all clients to disconnect.**

This can be mitigated by firing remotes to the server at a timed rate, so as to not exceed 60 hz.

lua

    local Timer = 0

    RunService.Heartbeat:Connect(function(DeltaTime)
        Timer += DeltaTime

        -- Only send events 60 times per second
        if Timer >= 1 / 60 then
            Timer = 0
            Zap.SendEvents()
        end
    end)

Note that Zap uses `RunService.Heartbeat` and a 61 hz rate by default.

### Default

`false`

### Options

  * `true`
  * `false`

### Example

Display in Playground

## `include_profile_labels`

When enabled, microprofiler lables are added to the generated file to diagnose performance issues.

### Default

`false`

### Options

  * `true`
  * `false`

### Example

Display in Playground

## `typescript_max_tuple_length`

The maximum non-nested length of tuples Zap can generate, with anything longer generating an array.

### Default

`10`

### Example

Display in Playground

## `typescript_enum` [`0.6.24+`]

This option allows the TypeScript output to generate `const enum` types instead of string literals.

### Default

`StringLiteral`

### Example

Display in Playground

### Options

In the example:

Display in Playground

`"StringLiteral"` will generate the following TypeScript type:

ts

    type RoundStatus = "Starting" | "Playing" | "Intermission"

`"ConstEnum"` will generate the following TypeScript type:

ts

    const enum RoundStatus {
        Starting,
        Playing,
        Intermission

WARNING

Because `const enum` emits numbers by default when compiled, setting `typescript_enum` to the `ConstEnum` option **will change** the Luau output to **accept numbers instead of strings** for an `enum`.

If you do not want this behavior use `"StringConstEnum"` or the default of `"StringLiteral"`.

`"StringConstEnum"` will generate the following TypeScript type:

ts

    const enum RoundStatus {
        Starting = "Starting",
        Playing = "Playing",
        Intermission = "Intermission"

## `yield_type`

This option changes the way functions yield in zap.

### Default

`"yield"`

### Options

INFO

The `"future"` option is not avaliable when `typescript` is enabled.

  * `"yield"`
  * `"future"`
  * `"promise"`

### Example

Display in Playground

## `async_lib`

INFO

This option is not required when `yield_type` is set to `yield`

WARNING

When using `typescript`, provide the path to the RuntimeLib.

This option provides the async library to Zap. The option must include a `require` statement, as it will be fed directly into the Luau code.

When using Futures, you must provide a path to [Future by red-blox](https://github.com/red-blox/Util/tree/main/libs/Future). As of writing, there are no other future libraries for Roblox.

Zap is also compatible with almost any Promise library. Some common examples are:

  * [Promise by evaera](https://github.com/evaera/roblox-lua-promise/)*
  * [Promise by Quenty](https://github.com/Quenty/NevermoreEngine/tree/main/src/promise)
  * [Promise by red-blox](https://github.com/red-blox/Util/tree/main/libs/Promise)

*The default in roblox-ts.

### Default

The path is empty.

### Example

Display in Playground

## `tooling`

This option determines if Zap should generate a file to allow it to inteface with other tooling. More information on this feature can be found on the [tooling intergration](https://zap.redblox.dev/usage/tooling.html) page.

### Default

`false`

### Example

Display in Playground

## `tooling_output`

This options allows you to configure where Zap will output its generated [tooling code](https://zap.redblox.dev/usage/tooling.html). If you're not using the CLI these options can be ignored.

The path is relative to the configuration file and should point to a lua(u) file.

### Default

Display in Playground

### Example

Display in Playground

or

Display in Playground

## `tooling_show_internal_data`

This option will add an additional element to the start of the [arguments array](https://zap.redblox.dev/usage/tooling.html#tooling-format-returns) returned by the [tooling intergration](https://zap.redblox.dev/usage/tooling.html) function. More information on its structure can be found [here](https://zap.redblox.dev/usage/tooling.html#tooling-format-returns).

### Default

`false`

### Example

Display in Playground

## `disable_fire_all`

This option determines if Zap should generate a `.FireAll` method on the server side. This is useful to turn off to prevent footguns if you're only ever sending events to loaded players.

### Default

`false`

### Example

Display in Playground


# Getting Started

## Installation

For users who wish to use Zap as a CLI, you can download the latest release from [GitHub Releases](https://github.com/red-blox/zap/releases/), or you can install it using [aftman](https://github.com/lpghatguy/aftman):

bash

    aftman add red-blox/zap

You can also use Zap in Studio with the community-made [Zappy](https://github.com/Ultrasonic1209/Zappy/) plugin.

Alternatively you can use the [web playground](https://zap.redblox.dev/playground) and move the generated files into your project.

## Writing Your First Network Description

Zap's IDL (Interface Description Language) is very simple, and you'll learn more about it further in this guide. For now, here's a simple example:

Display in Playground

## Generating Code

If you're using the playground your code will be generated automatically on every change. If you're using the CLI you can generate code by running:

bash

    zap path/to/config.zap

This will generate two files, `path/to/server/output.luau` and `path/to/client/output.luau`. You can then use these files in your project.

## Using the API

Zap's generated files return a table with all of their events. You can use this table to connect to events and fire them.


# Functions

Functions are another method of communication where the client can send arguments and have them returned by the server. For security, Zap only supports Client -> Server -> Client functions, not Server -> Client -> Server.

## Defining Functions

Functions are defined in your config file using the `funct` keyword.

Display in Playground

As you can see they have three fields. Let's go over them one by one:

### `call`

This field determines how the function is listened to on the server. The function will take the `args` as parameters and return `rets`.

  * `Async` functions can be listened to by one function, and they are called asynchronously.
  * `Sync` functions can be listened to by one function, and they are called synchronously.

DANGER

Synchronous functions are not recommended, and should only be used when performance is critical.

  * If a synchronous function callback yields it will cause **undefined and game-breaking behavior**.
  * If a synchronous function callback errors it will cause **the packet to be dropped**.

Use synchronous functions with extreme caution.

### `args`

This field determines the data that is sent to the server. It can be any [Zap type](https://zap.redblox.dev/config/types.html).

  * If the client doesn't send any data, the `args` field should be excluded.
  * Parameter names and parentheses are optional to preserve backwards compatibility. If parantheses are excluded, the function can only have one unnamed parameter.

Display in Playground

### `rets`

This field determines the data that is sent back to the client from the server. It can be any [Zap type](https://zap.redblox.dev/config/types.html).

  * If the server doesn't return any data, the `rets` field should be excluded.
  * Unlike `args`, `rets` cannot be named.
  * The function can return multiple values by separating each type with a comma and wrapping them all in parentheses:

Display in Playground


# Types

## Range Syntax

In some cases using the entire range of numbers isn't desirable and you'd rather limit the range. This can be done by using Zap's range syntax. This syntax will be very similar for users of Rust.

A full range is written by giving a minimum and maximum, separated by two dots. This looks like `0..100`.

One sided ranges are ranges where only the minimum or maximum is given. These look like `0..` or `..100`.

Exact ranges can be written by only giving the exact number, such as `0` or `100`.

Ranges can be written with no constraints by giving no minimum or maximum. This looks like `..`.

#### As a recap:

Range| Min| Max
---|---|---
`0..100`| 0| 100
`0..`| 0| ∞
`..100`| -∞| 100
`0`| 0| 0
`100`| 100| 100
`..`| -∞| ∞

TIP

Remember that ranges are always inclusive on the min and max.

## Numbers

Zap supports all buffer number types, signed integer, unsigned integer, and floating point.

### Signed Integers

Signed integers are standard integers. They can be positive and negative.

Type| Min Value| Max Value
---|---|---
`i8`| -128| 127
`i16`| -32,768| 32,767
`i32`| -2,147,483,648| 2,147,483,647

### Unsigned Integers

Unsigned integers are positive integers.

Type| Min Value| Max Value
---|---|---
`u8`| 0| 255
`u16`| 0| 65,535
`u32`| 0| 4,294,967,295

### Floating Point Numbers

Floating point numbers are numbers with a decimal point. They can be positive and negative.

Buffers support `f32` and `f64` floating point numbers, but unlike integers these numbers do not have a hard range. Instead the size determines the precision of the number. Determining what precision you need is out of scope for this documentation.

`f64`s are able to store integers accurately up to `2^53` (9,007,199,254,740,992). This is larger than the maximum value of `u32`, but also twice the size.

It should also be noted that the type of numbers in Luau is `f64`.

### Constraining Numbers

Numbers can be constrained by placing a range within parenthesis after the number type. For example, if you wanted to constrain a `u8` between `0` and `100` you could do:

Display in Playground

## Strings

Strings come in two variants: `string.utf8` for UTF-8 encoded strings, and `string.binary` for arbitrary binary strings. For example:

Display in Playground

Display in Playground

The length of strings can be constrained by placing a range within parenthesis after the string type. For example, if you wanted to constrain a UTF-8 string between `3` and `20` characters (like a username) you could do:

Display in Playground

Or for a binary string:

Display in Playground

WARNING

It's possible for usernames to exceed 20 characters under certain circumstances.

## Unknown

The keyword `unknown` represents data of a type that can't be known until runtime. For values that can be one of multiple known types, use tagged enums instead.

## Arrays

Arrays are defined as a type followed by square brackets. For example an array of `u8`s would be:

Display in Playground

The length of arrays can be constrained by placing a range within the square brackets. For example if you wanted to constrain the length of an array between `10` and `20` items you could do:

Display in Playground

## Maps

Maps are objects that have keys of one type, and values of another type.

Maps are defined using the `map` keyword, followed by a Luau-like map syntax. For example, a map of `string` keys and `u8` values would look like:

Display in Playground

## Sets

Sets are equivalent to a map where all values are `true`, and are defined using the `set` keyword. For example, a map of `string` keys to `true` would look like:

Display in Playground

## Enums

Zap has two types of enums, unit enums and tagged enums.

### Unit Enums

Unit enums are used to represent a set of possible values. They are defined using the `enum` keyword, followed by a set of possible string values. For example, a unit enum representing the status of a round would look like:

Display in Playground

This code would then create the Luau type:

lua

    type RoundStatus = "Starting" | "Playing" | "Intermission"

### Tagged Enums

Tagged enums will be very familiar to Rust users.

Tagged enums are a set of possible variants, each with attached data. They are defined using the `enum` keyword, followed by a string which is the tag field name. After the tag field name, a set of variants are defined. Each variant is defined by a string tag, followed by a struct. Variants must be separated by a comma. Trailing commas are allowed.

Display in Playground

This code would create the Luau type:

lua

    type t = { type: "number", value: number }
        | { type: "string", value: string }
        | { type: "boolean", value: boolean }

Tagged enums allow you to pass different data depending on a variant. They are extremely powerful and can be used to represent many different types of data.

## Structs

Structs are similar to Interfaces, and are a collection of statically named fields with different types.

To define a struct, use the `struct` keyword followed by a Luau interface-like syntax. For example, a struct representing an item in a shop would look like:

Display in Playground

## Instances

Roblox Instances can be passed through Zap.

DANGER

If a non-optional instance results in `nil` when received, it will cause a deserialize error and the packet will be dropped. Instances are turned into `nil` when they don't exist on the reciever - for example: an instance from the server that isn't streamed into a client or an instance that only exists on the client.

Immediately before making important changes (such as deletion or parenting to non-replicated storage) to non-optional `Instance`s that are referenced in events fired to clients, consider calling `SendEvents` to flush the outgoing queue. This way, even with batching, your Zap events are guaranteed to arrive before deletion replicates.

If you want to send an instance that may not exist, you must make it optional.

Display in Playground

You can also specify what kind of instance you want to accept by using dot notation with the class name, for example:

Display in Playground

Classes that inherit your specified class will be accepted, for example `Part`.

## CFrames

Zap supports sending CFrames. There are two types of CFrame you may send - a regular `CFrame`, and an `AlignedCFrame`.

CFrame rotation matrices are compressed using the axis-angle representation.

DANGER

CFrames are orthonormalized when sent. If you need to send a CFrame that is not orthogonal, i.e. one that does not have a valid rotation matrix, it is recommended to send the components and reconstruct it on the other side. Note that use cases for this are exceedingly rare and you most likely will not have to worry about this, as the common CFrame constructors only return orthogonal CFrames.

### Aligned CFrames

When you know that a CFrame is going to be axis-aligned, it is preferrable to use the `AlignedCFrame` type.

It uses much less bandwidth, as the rotation can just be represented as a single byte Enum of the possible axis aligned rotations.

You can think of an axis-aligned CFrame as one whose LookVector, UpVector, and RightVector all align with the world axes in some way. Even if the RightVector is facing upwards, for example, it would still be axis-aligned.

Position does not matter at all, only the rotation.

DANGER

If the CFrame is not axis aligned then Zap will throw an error, so make sure to use this type carefully! Don't let this dissuade you from using it though, as the bandwidth savings can be significant.

Here are some examples of axis-aligned CFrames.

lua

    local CFrameSpecialCases = {
        CFrame.Angles(0, 0, 0),
        CFrame.Angles(0, math.rad(180), math.rad(90)),
        CFrame.Angles(0, math.rad(-90), 0),
        CFrame.Angles(math.rad(90), math.rad(-90), 0),
        CFrame.Angles(0, math.rad(90), math.rad(180)),
        -- and so on. there are 24 of these in total.

## Vectors `[0.6.17+]`

Zap supports `vector`s with any numeric components other than `f64`. The Z component is optional, and will result in a `0` if omitted.

Display in Playground

Display in Playground

Omitting all components will emit `vector(f32, f32, f32)`.

Display in Playground

Zap also supports serializing `Vector3`, and to not serialise the `Z` property of a `Vector3`, you can use the `Vector2` type.

## DateTimes

Zap supports sending DateTimes. There are two types of DateTime you may send - a regular `DateTime`, and `DateTimeMillis`. DateTime sends the UnixTimestamp property of the DateTime object, with DateTimeMillis sending the UnixTimestampMillis property of the DateTime object.

## Other Roblox Classes

The following Roblox Classes are also available as types in Zap:

  * `Color3`
  * `BrickColor`

## Optional Types

A type can be made optional by appending a `?` after the **whole type**, such as:

Display in Playground

## OR/Unions [0.6.20+]

Zap supports union types, or "OR" types. They work by checking the type of the data at runtime, and serializing it correspondingly. Since the type is checked at runtime, you're limited to using only one of the runtime type, that is you cannot have both `u8` and `u16`.

For example, the following type:

Display in Playground

Will check (& serialize the values separately) in the following way:

  1. If it's `"hello"`
  2. If it's `"world"`
  3. If it's a table and it has `tag = "one"`
  4. If it's a table and it has `tag = "two"`
  5. If it's a string
  6. If it's a number
  7. If it's an instance, and of a Player
  8. If it's an instance
  9. If it's nil*
  10. If all else fails, serialize it as `unknown`

*Unions being optional is implied by containing `unknown`.

Additionally, you may not use optional types inside the union itself, rather you need to make the whole union optional.


# Events

Events are the primary method of communication between the client and the server. Events are also what is exposed to the developer from Zap's generated API.

## Defining Events

Events are defined in your config file using the `event` keyword.

Display in Playground

As you can see they have four fields. Let's go over them one by one:

### `from`

This field determines which side of the game can fire the event. It can be either `Server` or `Client`.

At this time Zap does not support two way events. As events have almost no overhead, feel free to add more events instead of using two way events.

### `type`

This field determines the type of event. It can be either `Reliable` or `Unreliable`.

  * Reliable events are guaranteed to arrive at their destination in the order they were sent.
  * Unreliable events are not guaranteed to arrive at their destination, and they are not guaranteed to arrive in the order they were sent. Unreliable events also have a maximum size of 1000 bytes.

### `call`

This field determines how the event is listened to on the receiving side.

  * `ManyAsync` events can be listened to by many functions, and they are called asynchronously.
  * `ManySync` events can be listened to by many functions, and they are called synchronously.
  * `SingleAsync` events can be listened to by one function per actor, and they are called asynchronously.
  * `SingleSync` events can be listened to by one function per actor, and they are called synchronously.
  * `Polling` `[0.6.18+]` events are received once per actor by iterating through `event.iter()`.

DANGER

Synchronous events are not recommended, and should only be used when performance is critical.

  * If a synchronous event callback yields it will cause **undefined and game-breaking behavior**.
  * If a synchronous event callback errors it will cause **the packet to be dropped**.

Use synchronous events with extreme caution.

### `data`

This field determines the data that is sent with the event. It can be any [Zap type](https://zap.redblox.dev/config/types.html).

  * If the event does not require any data, the `data` field should be excluded.
  * Parameter names and parentheses are optional to preserve backwards compatibility. If parantheses are excluded, the event can only have one unnamed parameter.

Display in Playground


# What is Zap?

Zap is a tool to generate highly efficient networking code for Roblox. It has no compromises on performance or developer experience. Zap takes a description of your data and event, and generates code specific to your game.

Already convinced? Head over to [getting started](https://zap.redblox.dev/intro/getting-started.html).

## ⚡ Performance

Zap is designed to be incredibly fast, but what does this mean in terms of networking?

### Bandwidth Usage

The most important metric when it comes to networking is bandwidth usage. Zap packs all data into buffers, which on top of using less space for the same data, also compress when passed through RemoteEvents.

### CPU Usage

All of this saved bandwidth comes at a cost, right? Not exactly. Zap generates code specific to your game, which means it can use the most efficient way to pack and unpack data. This means minimal branching and minimal calls.

## 🧑💻 Developer Experience

Zap has an unparalleled developer experience. From writing your network description to using the API, Zap is a joy to use.

### Writing your Network Description

Zap's IDL is easy to learn and easy to use. It's simple while still being expressive and powerful. If you mess up, it's okay. Zap provides helpful error messages to get you back on track.

### Using the API

Zap's API is fully typesafe, with Luau or TypeScript. You'll recieve full type checking and autocompletion in your editor.

### Extended Type Support

Zap supports far more types than normal `RemoteEvent` serialization does. Maps with numbers or `Instance`s as keys are good examples.

## 🔒 Security

Zap is fully secure. Buffers make reverse engineering your game's networking much harder and Zap validates all data received.

### Buffers

Buffers are byte arrays that Zap uses to send data. They are _not_ human readable, and are much harder to reverse engineer than Roblox's self-describing encoding. Zap's encoded data changes based on the data being sent, which makes it even harder to read.

### Validation

Zap validates all data recieved. If a client sends invalid data, Zap will catch it before it reaches your game code. This means you don't have to worry about malicious clients sending invalid data.


Vide is a reactive Luau UI library inspired by [Solid](https://www.solidjs.com/).

- Fully Luau typecheckable
- Declarative and concise syntax.
- Reactively driven.

## Getting started

Read the
[crash course](https://centau.github.io/vide/tut/crash-course/1-introduction)
for a quick introduction to the library.

## Code sample

```luau
local create = vide.create
local source = vide.source

local function Counter()
    local count = source(0)

    return create "TextButton" {
        Text = function()
            return "count: " .. count()
        end,

        Activated = function()
            count(count() + 1)
        end
end


Skip to content

# Vide

A reactive UI library for Luau.

[Tutorials](https://centau.github.io/vide/tut/crash-course/1-introduction.html)

[API Reference](https://centau.github.io/vide/api/reactivity-core.html)


# Introduction

This is a tutorial that introduces the concepts and usage of Vide.

Vide is heavily inspired by [Solid](https://www.solidjs.com/).

## Why Vide?

Vide's reactive and declarative API aims to let you program UI as simply as possible, with a strong focus on how data flows through your application.

Some of Vide's main design choices:

  * Syntax minimal.
  * Data oriented.
  * Typechecking compatible.
  * Instance independent.

Vide's reactivity operates with the concept of scopes which carries a learning curve, though is what makes Vide's minimal syntax possible. The crash course will introduce these concepts gradually.