Skip to main content

Roam

Roam is a systems bootstrapping tool for Roblox luau projects, designed to make it easy to initialize and start services in a topologically sorted manner without the need to manually order and start services.

Roam follows a design pattern similar to Knit, but is more lightweight. It removes all networking and replication functionality, and instead focuses on providing a simple methodology to easily initialize Services given to it.

Roam gathers a collection of specified services and initializes 'syncronously'. Once all services have been fully initialized, it then starts them 'asyncronously' by spawning their 'RoamStart' method in a new thread.

Roam is RunContext agnostic, meaning it can be used on both the server and client in the same manner. It makes no distinction between the two, and instead focuses on providing a simple interface for initializing and starting services. This means you could create a service and register it on both the server and client, and it will be initialized and started on both ends.

[CONTRACTS]

  • Services must be created/registered before Roam is started.
  • Services must be created/registered with a unique name.
  • Services with RoamInit and RoamStart methods will have those methods called when Roam is started at the appropriate time. (Names are configurable)
  • RequiredServices boot in proper topological order if specified in the ServiceConfig.
  • Roam functions the same regardless of RunContext (Server/Client).

[EXAMPLE STARTUP]

local Roam = require(ReplicatedStorage.Roam)

-- Just iterates through all the children of the given parents
-- and requires any module scripts that match the given predicate
Roam.requireModules({
	ReplicatedStorage.Shared;
	ServerScriptService.Server;
})

-- Start Roam
Roam.start()
:andThenCall(print, "Roam started!")
:catch(warn)
Setting up Services

Services can be set up in a variety of ways. The most common way is to create a ModuleScript that returns a table with the methods you want to define, and then register it with Roam just prior to the final module's return.

See Roam.registerService for more information on setting up a new service.

Networking

Roam does not inherently have networking functionality. However, it can easily be added through the use of NetWire's .setupServiceNetworking funtion.

Types

ServiceConfig

interface ServiceConfig {
Namestring--

Name of the Service. Must be unique. Used when accessing via .getService

RequiredServices{Service}?--

The Services that this Service depends on. Roam will ensure that these Services are initialized before this Service.

StartMethodNamestring?--

Overrides default StartMethodName of "RoamStart"

InitMethodNamestring?--

Overrides default InitMethodName of "RoamInit"

}
local myOtherService = require(ReplicatedStorage.MyOtherService)

-------------------------------------------------

local MyService = {}

function MyService:CustomStartMethod()
	print("MyService started!")
end

-------------------------------------------------

Roam.registerService(MyService, {
	Name = "MyService",
	RequiredServices = {myOtherService},
	StartMethodName = "CustomStartMethod",
})

return MyService
Deffering RequiredServices

Do NOT add services to the RequiredServices after you have created or registered the service. This will cause undefined behavior.

Properties

Debug

Roam.Debug: boolean

Whether or not to print debug messages. Default is false.

Bootstrappers

Roam.Bootstrappers: {
Server(scriptScript) → (),
Client(scriptScript) → ()
}

A table of generic bootstrappers for Roam that you can use to quickly setup new projects.

local Roam = require(Packages.Roam)

Roam.Bootstrappers.Server(script)
:andThenCall(print, "Roam Server Bootstrapped!")

Functions

registerService

Roam.registerService(
serviceService,
serviceConfig(ServiceConfig | string)?
) → Service

Registers a Service/Table with Roam to be Initialized and Started when Roam starts. Cannot be called after Roam has been started.

local MyRegisteredService = {}

function MyRegisteredService:RoamStart()
	print("MyRegisteredService started!")
end

function MyRegisteredService:RoamInit()
	print("MyRegisteredService initialized!")
end

----------------------------------------------------------------

local Roam = require(Packages.Roam)
Roam.registerService(MyRegisteredService, "MyRegisteredService")

return MyRegisteredService

start

Roam.start(postInitPreStart(() → (Promise?))?) → Promise

Starts Roam. Should only be called once. Calling multiple times will result in a promise rejection.

Optional argument postInitPreStart is a function that is called after all services have been initialized, but before they are started.

Roam.start()
:andThenCall(print, "Roam started!")
:catch(warn)
caution

Be sure that all services have been created before calling Start. Services cannot be added later.

Bootstrapping

You can use the Roam.Bootstrappers table/methods to quickly bootstrap Roam in your project. This is reccomended as it will provide a consistent starting point for your projects.

onStart

Roam.onStart() → Promise

Returns a promise that is resolved once Roam has started. This is useful for any code that needs to tie into Roam services but is not the script that called Start.

Roam.onStart():andThen(function()
	local MyService = Roam.Services.MyService
	MyService:DoSomething()
end):catch(warn)

isReady

Roam.isReady() → boolean

Returns whether or not Roam has been successfully started and is ready for external access.

requireModules

Roam.requireModules(
parentsInstance | {Instance},
config{
DeepSearchboolean?,
RequirePredicate((objModuleScript) → boolean)?,
IgnoreDescendantsPredicate((objInstance) → boolean)?,
}?
) → {Service}

Requires all the modules that are children of the given parent. This is an easy way to quickly load all services that might be in a folder. Takes an optional predicate function to filter which modules are loaded. Services collected this way must not yield.

  • DeepSearch -> whether it checks descendants or just children
  • RequirePredicate -> a predicate function that determines whether a module should be required
  • IgnoreDescendantsPredicate -> A Predicate for whether the Descendants of the Module should be Searched (Only matters if DeepSearch is true)
local pred = function(obj: ModuleScript): boolean
	return obj.Name:match("Service$") ~= nil
end

Roam.requireModules(ReplicatedStorage.Shared, {
	DeepSearch = true,
	RequirePredicate = pred,
	IgnoreDescendantsPredicate = function(obj: Instance): boolean
		return obj.Name == "Ignore"
	end,
})

getNameFromService

Roam.getNameFromService(serviceService) → string

Fetches the name of a registered Service.

Show raw api
{
    "functions": [
        {
            "name": "registerService",
            "desc": "Registers a Service/Table with Roam to be Initialized and Started when Roam starts.\nCannot be called after Roam has been started.\n\n```lua -- MyRegisteredService.lua\nlocal MyRegisteredService = {}\n\nfunction MyRegisteredService:RoamStart()\n\tprint(\"MyRegisteredService started!\")\nend\n\nfunction MyRegisteredService:RoamInit()\n\tprint(\"MyRegisteredService initialized!\")\nend\n\n----------------------------------------------------------------\n\nlocal Roam = require(Packages.Roam)\nRoam.registerService(MyRegisteredService, \"MyRegisteredService\")\n\nreturn MyRegisteredService\n```",
            "params": [
                {
                    "name": "service",
                    "desc": "",
                    "lua_type": "Service"
                },
                {
                    "name": "serviceConfig",
                    "desc": "",
                    "lua_type": "(ServiceConfig | string)?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Service\r\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 242,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "createService",
            "desc": "Creates a Service/Table with Roam to be Initialized and Started when Roam starts.\nCannot be called after Roam has been started.\n\nThis is an alternative method to setting up services over using `registerService`.\n\n```lua\nlocal Roam = require(ReplicatedStorage.Roam)\n\nlocal MyService = Roam.createService { Name = \"MyService\" }\n\nfunction MyService:DoSomething()\n\tprint(\"yeee haw!\")\nend\n\n-- Default StartMethodName is \"RoamStart\" (Can be overriden in service creation config)\nfunction MyService:RoamStart()\n\tprint(\"MyService started!\")\n\tself:DoSomething()\nend\n\n-- Default InitMethodName is \"RoamInit\" (Can be overriden in service creation config)\nfunction MyService:RoamInit()\n\tprint(\"MyService initialized!\")\nend\n\nreturn MyService\n```",
            "params": [
                {
                    "name": "serviceDef",
                    "desc": "",
                    "lua_type": "ServiceConfig"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Service\r\n"
                }
            ],
            "function_type": "static",
            "deprecated": {
                "version": "0.1.5",
                "desc": null
            },
            "private": true,
            "source": {
                "line": 309,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "start",
            "desc": "Starts Roam. Should only be called once. Calling multiple times will result in a promise rejection.\n\nOptional argument `postInitPreStart` is a function that is called\nafter all services have been initialized, but before they are started.\n\n```lua\nRoam.start()\n:andThenCall(print, \"Roam started!\")\n:catch(warn)\n```\n\n:::caution\nBe sure that all services have been created _before_\ncalling `Start`. Services cannot be added later.\n:::\n\n:::tip Bootstrapping\nYou can use the [Roam.Bootstrappers](Roam#Bootstrappers) table/methods to quickly bootstrap Roam in your project.\nThis is reccomended as it will provide a consistent starting point for your projects.\n:::",
            "params": [
                {
                    "name": "postInitPreStart",
                    "desc": "",
                    "lua_type": "(() -> (Promise?))?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 357,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "onStart",
            "desc": "Returns a promise that is resolved once Roam has started. This is useful\nfor any code that needs to tie into Roam services but is not the script\nthat called `Start`.\n```lua\nRoam.onStart():andThen(function()\n\tlocal MyService = Roam.Services.MyService\n\tMyService:DoSomething()\nend):catch(warn)\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 499,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "isReady",
            "desc": "Returns whether or not Roam has been successfully started and is ready for external access.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\r\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 510,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "requireModules",
            "desc": "Requires all the modules that are children of the given parent. This is an easy\nway to quickly load all services that might be in a folder. Takes an optional predicate\nfunction to filter which modules are loaded. Services collected this way must not yield.\n- `DeepSearch` -> whether it checks descendants or just children\n- `RequirePredicate` -> a predicate function that determines whether a module should be required\n- `IgnoreDescendantsPredicate` -> A Predicate for whether the Descendants of the Module should be Searched (Only matters if DeepSearch is true)\n\n```lua\nlocal pred = function(obj: ModuleScript): boolean\n\treturn obj.Name:match(\"Service$\") ~= nil\nend\n\nRoam.requireModules(ReplicatedStorage.Shared, {\n\tDeepSearch = true,\n\tRequirePredicate = pred,\n\tIgnoreDescendantsPredicate = function(obj: Instance): boolean\n\t\treturn obj.Name == \"Ignore\"\n\tend,\n})\n```",
            "params": [
                {
                    "name": "parents",
                    "desc": "",
                    "lua_type": "Instance | { Instance }"
                },
                {
                    "name": "config",
                    "desc": "",
                    "lua_type": "{\r\n\t\tDeepSearch: boolean?,\r\n\t\tRequirePredicate: ((obj: ModuleScript) -> boolean)?,\r\n\t\tIgnoreDescendantsPredicate: ((obj: Instance) -> boolean)?,\r\n\t}?\r\n"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ Service }\r\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 536,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "getNameFromService",
            "desc": "Fetches the name of a registered Service.",
            "params": [
                {
                    "name": "service",
                    "desc": "",
                    "lua_type": "Service"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string\r\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 590,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "getService",
            "desc": "Fetches a registered Service by name.\nCannot be called until Roam has been started.",
            "params": [
                {
                    "name": "serviceName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Service\r\n"
                }
            ],
            "function_type": "static",
            "deprecated": {
                "version": "0.1.5",
                "desc": null
            },
            "private": true,
            "source": {
                "line": 600,
                "path": "lib/roam/src/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "ClassName",
            "desc": "The ClassName of the Roam module.",
            "lua_type": "\"Roam\"",
            "tags": [
                "ReadOnly"
            ],
            "private": true,
            "source": {
                "line": 200,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "Debug",
            "desc": "Whether or not to print debug messages. Default is false.",
            "lua_type": "boolean",
            "source": {
                "line": 206,
                "path": "lib/roam/src/init.luau"
            }
        },
        {
            "name": "Bootstrappers",
            "desc": "A table of generic bootstrappers for Roam that you can use to quickly setup new projects.\n```lua\nlocal Roam = require(Packages.Roam)\n\nRoam.Bootstrappers.Server(script)\n:andThenCall(print, \"Roam Server Bootstrapped!\")\n```",
            "lua_type": "{Server: (script: Script) -> (), Client: (script: Script) -> ()}",
            "source": {
                "line": 218,
                "path": "lib/roam/src/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "ServiceConfig",
            "desc": "```lua\nlocal myOtherService = require(ReplicatedStorage.MyOtherService)\n\n-------------------------------------------------\n\nlocal MyService = {}\n\nfunction MyService:CustomStartMethod()\n\tprint(\"MyService started!\")\nend\n\n-------------------------------------------------\n\nRoam.registerService(MyService, {\n\tName = \"MyService\",\n\tRequiredServices = {myOtherService},\n\tStartMethodName = \"CustomStartMethod\",\n})\n\nreturn MyService\n```\n\n:::caution Deffering RequiredServices\nDo NOT add services to the RequiredServices after you have created or registered the service. This will cause undefined behavior.\n:::",
            "fields": [
                {
                    "name": "Name",
                    "lua_type": "string",
                    "desc": "Name of the Service. Must be unique. Used when accessing via .getService"
                },
                {
                    "name": "RequiredServices",
                    "lua_type": "{Service}?",
                    "desc": "The Services that this Service depends on. Roam will ensure that these Services are initialized before this Service."
                },
                {
                    "name": "StartMethodName",
                    "lua_type": "string?",
                    "desc": "Overrides default StartMethodName of \"RoamStart\""
                },
                {
                    "name": "InitMethodName",
                    "lua_type": "string?",
                    "desc": "Overrides default InitMethodName of \"RoamInit\""
                }
            ],
            "source": {
                "line": 115,
                "path": "lib/roam/src/init.luau"
            }
        }
    ],
    "name": "Roam",
    "desc": "Roam is a systems bootstrapping tool for Roblox luau projects, designed to make it easy to\ninitialize and start services in a topologically sorted manner without the need to\nmanually order and start services.\n\nRoam follows a design pattern similar to [Knit](https://sleitnick.github.io/Knit/), but is more lightweight. It removes all networking and replication\nfunctionality, and instead focuses on providing a simple methodology to easily\ninitialize Services given to it.\n\nRoam gathers a collection of specified services and initializes 'syncronously'.\nOnce all services have been fully initialized, it then starts them 'asyncronously' by\nspawning their 'RoamStart' method in a new thread.\n\nRoam is RunContext agnostic, meaning it can be used on both the server and client in the same manner.\nIt makes no distinction between the two, and instead focuses on providing a simple\ninterface for initializing and starting services. This means you could create a service and register it on \nboth the server and client, and it will be initialized and started on both ends. \n\n**[CONTRACTS]**\n- Services must be created/registered before Roam is started.\n- Services must be created/registered with a unique name.\n- Services with `RoamInit` and `RoamStart` methods will have those methods\n  called when Roam is started at the appropriate time. (Names are configurable)\n- `RequiredServices` boot in proper topological order if specified in the ServiceConfig.\n- Roam functions the same regardless of RunContext (Server/Client).\n\n**[EXAMPLE STARTUP]**\n```lua -- ServerBootstrapper.Server.lua\nlocal Roam = require(ReplicatedStorage.Roam)\n\n-- Just iterates through all the children of the given parents\n-- and requires any module scripts that match the given predicate\nRoam.requireModules({\n\tReplicatedStorage.Shared;\n\tServerScriptService.Server;\n})\n\n-- Start Roam\nRoam.start()\n:andThenCall(print, \"Roam started!\")\n:catch(warn)\n```\n\n:::info Setting up Services\nServices can be set up in a variety of ways. The most common way is to create a ModuleScript\nthat returns a table with the methods you want to define, and then register it with Roam just prior\nto the final module's return.\n\nSee [Roam.registerService](Roam#registerService) for more information on setting up a new service.\n:::\n\n:::tip Networking\nRoam does not inherently have networking functionality. However, it can easily be added through the use of NetWire's \n**[.setupServiceNetworking](https://raild3x.github.io/ModulesOnRails/api/ServerNetWire/#setupServiceNetworking)** funtion.\n:::",
    "source": {
        "line": 62,
        "path": "lib/roam/src/init.luau"
    }
}