Module System
The Module System is the core of Riptide’s dependency injection. It automatically require()s all ModuleScript descendants in your configured folders, registers them under canonical IDs, and provides type-safe lookup methods.
type Config = { ModulesFolder: Folder | { Folder }, SharedModulesFolder: (Folder | { Folder })?, ComponentsFolder: Folder?,}The internal registry types:
-- Internal module entry{ name: string, module: any }
-- Registry (not directly accessible)_modules: { [string]: any } -- canonicalId → module table_moduleAliases: { [string]: string | false } -- shortName → canonicalId | falseModule Lookup Methods
Section titled “Module Lookup Methods”Lifecycle hooks receive the side-specific API directly. Server services get Riptide.Server; client controllers get Riptide.Client. Entry scripts can still import the side explicitly with require(...).Server or require(...).Client.
GetModule
Section titled “GetModule”Riptide.GetModule(name: string) -> anyUniversal module lookup. Works on both server and client. Resolves in this order:
- Exact match against canonical ID (e.g.,
"Economy/PlayerData") - Alias match against short name (e.g.,
"PlayerData") - Returns
nil+ warning if not found or ambiguous
local data = Riptide.GetModule("Economy/PlayerData")GetService
Section titled “GetService”Riptide.GetService(name: string) -> anyServer-side alias for GetModule. Throws an error if called on the client.
-- In a server service Init:function MyService:Init(Riptide) self.DataService = Riptide.GetService("DataService")endGetController
Section titled “GetController”Riptide.GetController(name: string) -> anyClient-side alias for GetModule. Throws an error if called on the server.
-- In a client controller Init:function UIController:Init(Riptide) self.InputController = Riptide.GetController("InputController")endCanonical Module IDs
Section titled “Canonical Module IDs”Modules are identified by their relative path from the modules folder, using / as separator:
ModulesFolder/├── Economy/│ └── PlayerData.lua → "Economy/PlayerData"├── Combat/│ └── DamageService.lua → "Combat/DamageService"└── MatchService.lua → "MatchService"Alias Resolution
Section titled “Alias Resolution”Short names (the ModuleScript.Name) are automatically created as aliases:
| Scenario | Result |
|---|---|
| Name is unique across all folders | Alias works. GetModule("PlayerData") → ✅ |
| Name collides with another module | Alias is disabled (marked false). A warning is emitted. Use canonical ID. |
| Canonical ID is the same as name | No alias needed. Direct match. |
Loading Order
Section titled “Loading Order”When Launch is called:
- Plugins (
PluginsFolder/ExternalPlugins) are loaded and initialized first. Plugins are framework extensions, so theirPublicAPIand sandbox registrations are available before game modules load. - Shared modules (
SharedModulesFolder) are loaded. - Side-specific modules (
ModulesFolder) are loaded. - ComponentService starts if
ComponentsFolderis configured. - Module Init runs synchronously.
- Plugin Start readiness completes before player lifecycle replay and game module
Start. - PlayerLifecycle starts on the server and replays existing players.
- Module Start is dispatched asynchronously.
If a ModuleScript appears in multiple module folders (for example through linked instances), it is only loaded once.
Entry Point Types
Section titled “Entry Point Types”The framework exports clean types for use in your modules:
export type RiptideServer = { Network: NetworkServerAPI, Signal: typeof(Signal), Async: typeof(Async), ComponentService: ComponentServiceAPI, State: StateReplicationServerAPI, PlayerLifecycle: any, GetModule: (name: string) -> any, GetService: (name: string) -> any, Launch: (config: Config) -> (),}
export type RiptideClient = { Network: NetworkClientAPI, Signal: typeof(Signal), Async: typeof(Async), ComponentService: ComponentServiceAPI, State: StateReplicationClientAPI, GetModule: (name: string) -> any, GetController: (name: string) -> any, Launch: (config: Config) -> (),}
export type Riptide = { Server: RiptideServer?, Client: RiptideClient?,}Use in your modules for full autocomplete:
local RiptidePkg = require(ReplicatedStorage.Packages.Riptide)type RiptideServer = RiptidePkg.RiptideServertype RiptideClient = RiptidePkg.RiptideClientThe root package still exposes compatibility aliases, but new entry scripts should prefer require(...).Server or require(...).Client. Lifecycle hooks receive that same side-specific API directly, so module code can use Riptide.Network, Riptide.State, and Riptide.GetService / Riptide.GetController without repeating .Server or .Client.