Core Concepts
API Conventions
The patterns every Engine API follows. Naming, Async methods and Promises, Get versus Find, options tables, results with reasons, events, properties, errors and frozen tables.
Engine's modules are written to the same rules, so once you know one, the others feel familiar.
Naming
Engine follows Roblox's style:
- Methods are PascalCase and called with a colon:
DataService:CreateStore("Settings"). - Package constructors use a dot:
Signal.new(),Promise.resolve(value). - Properties and events are PascalCase too:
ModerationSystem.SlowMode,EconomySystem.BalanceChanged.
Async methods return Promises
A method whose name ends in Async may take time (DataStores, web calls, other servers) and returns a Promise instead of yielding:
local Settings = DataService:CreateStore("Settings")
Settings:GetAsync("Global")
:andThen(function(settings)
apply(settings)
end)
:catch(function(err)
warn("Could not load settings:", err)
end)Inside a thread that may yield, read the result directly with :await() (returns ok, ...) or :expect() (errors on rejection):
local ok, settings = Settings:GetAsync("Global"):await()Many actions come in pairs. The plain method returns right away; the Async one resolves when the work is finished or saved:
local sanction = ModerationSystem:Ban(player, "Exploiting") -- applied now
ModerationSystem:BanAsync(userId, "Exploiting"):andThen(onSaved) -- resolves once savedGet, Find and lists
| Pattern | Returns | When it is missing |
|---|---|---|
:GetX(name) | The object | Errors with a message that says how to create it. |
:FindX(name) | The object or nil | Returns nil. |
:GetXs() | A list of all of them | Returns an empty list. |
Use Get when the object must exist (a typo should fail loudly) and Find when it may not.
Create and Define make new objects (CreateShop, DefineRole), and objects you create have a :Destroy() method.
Options tables
Methods with more than one or two optional parameters take an options table, so calls read clearly and new options never break your code:
EconomySystem:CreateCurrency("Gems", { Symbol = "G", Max = 100000, Decimals = 0 })Unknown keys are ignored. Wrong types (a string where a number belongs) error immediately.
Expected failures return a reason
Mistakes in your code (wrong argument types, unknown names) error. Things that can legitimately fail at runtime return a reason instead, so you can handle them without pcall:
local result = EconomySystem:Withdraw(player, "Coins", 500)
if not result.Ok then
print(result.Reason) -- "Insufficient"
end
local sanction, denied = ModerationSystem:Kick(target, { By = moderator })
if not sanction then
print(denied) -- "TargetOutranks"
endAsync methods reject their Promise with the same reason strings. Many modules publish their reasons as constants, such as ModerationSystem.ReportErrors or PasswordSystem.PolicyReasons.
Events are Signals
Every event is a Signal:
local connection = EconomySystem.BalanceChanged:Connect(function(player, currency, balance, delta)
print(player.Name, currency, balance)
end)
connection:Disconnect()
EconomySystem.TradeCreated:Once(onFirstTrade)
local trade = EconomySystem.TradeCreated:Wait()Handlers run in their own threads, so a handler that yields or errors does not affect others.
Properties
Modules expose state as properties. Read-only properties raise an error when assigned; settable ones apply the change right away:
print(ModerationSystem.LoadedRecordCount)
ModerationSystem.SlowMode = 5 -- same as :SetSlowMode(5)Reading or writing a member that does not exist errors (Attempt to get ModerationSystem::SlowMod (not a valid member)), so typos are caught immediately instead of silently returning nil.
Errors
Engine's errors start with Engine: and name the module and method, so you can tell where they came from:
Engine: EconomySystem:CreateShop "Weapons" - Items must be a list of itemsFrozen snapshots
Tables Engine hands back (records, results, descriptions) are often frozen snapshots. They do not change when the underlying data changes, and writing to them errors. Copy them with TableKit if you need a mutable version, and use the module's methods to make changes.
Types
Each module exports its types, listed in the Types section at the bottom of its page. Engine's getters return each module without a static type, so annotate your variables where it helps:
local Signal = Engine:GetPackage("Signal")
local onRoundEnd: typeof(Signal.new()) = Signal.new()