Server Module Loader Design Review

Hello, recently I built a module loader (server-side) and would like to hear some opinions on it, as well as suggestions on what could be improved or where I might be doing things wrong.

Also, I’m wondering if it’s worth using strict type checking and annotating types everywhere.

local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local AttributeUtils = require(ReplicatedStorage.Shared.Modules.Core.AttributeUtils)
local DataManager = require(ServerScriptService.Data.DataManager)

local moduleFolders = {
	ServerScriptService:WaitForChild("Controllers"),
	ServerScriptService:WaitForChild("Services")
}

local modules = {}
local playerHandlers = {}
local characterHandlers = {}

local initializedModules = {}
local handledPlayers = {}
local connections = {}

local function sortByPriority(list)
	table.sort(list, function(a, b)
		return (a.Priority or 0) > (b.Priority or 0)
	end)
end

local function registerModule(moduleScript)
	table.insert(modules, moduleScript)

	if moduleScript.SetupPlayer then
		table.insert(playerHandlers, moduleScript)
	end

	if moduleScript.SetupCharacter then
		table.insert(characterHandlers, moduleScript)
	end
end

local function gatherModules()
	for _, folder in ipairs(moduleFolders) do
		for _, obj in ipairs(folder:GetDescendants()) do
			if not obj:IsA("ModuleScript") then
				continue
			end

			local success, moduleScript = pcall(require, obj)
			if not success or type(moduleScript) ~= "table" or not moduleScript.Init then
				continue
			end

			moduleScript.Priority = moduleScript.Priority or 0
			registerModule(moduleScript)
		end
	end

	sortByPriority(modules)
	sortByPriority(playerHandlers)
	sortByPriority(characterHandlers)
end

local function initModules()
	for _, moduleScript in ipairs(modules) do
		if not initializedModules[moduleScript] then
			initializedModules[moduleScript] = true
			moduleScript.Init()
		end
	end
end

local function runCharacterModules(player, character)
	if character:GetAttribute("Initialized") then
		return
	end

	character:SetAttribute("Initialized", true)

	for _, moduleScript in ipairs(characterHandlers) do
		moduleScript:SetupCharacter(player, character)
	end
end

local function cleanupPlayer(player)
	if connections[player] then
		for _, conn in ipairs(connections[player]) do
			conn:Disconnect()
		end
		connections[player] = nil
	end

	handledPlayers[player] = nil
end

local function track(player, conn)
	connections[player] = connections[player] or {}
	table.insert(connections[player], conn)
end

local function handlePlayer(player)
	if handledPlayers[player] then return end
	handledPlayers[player] = true

	local loaded = AttributeUtils.WaitForAttributes(player, {"DataLoaded"}, 10)
	if not loaded and not DataManager.Profiles[player] then
		warn("Data not loaded for " .. player.Name .. " (timeout)")
		return
	end

	for _, moduleScript in ipairs(playerHandlers) do
		moduleScript:SetupPlayer(player)
	end

	track(player, player.CharacterAdded:Connect(function(character)
		runCharacterModules(player, character)
	end))

	if player.Character then
		runCharacterModules(player, player.Character)
	end
end

gatherModules()
initModules()

Players.PlayerAdded:Connect(handlePlayer)
Players.PlayerRemoving:Connect(cleanupPlayer)

for _, player in ipairs(Players:GetPlayers()) do
	task.spawn(handlePlayer, player)
end

strict type checking is okay to use as i think reduces future errors by warning you in script analysis beforehand, type annotating just helps you catch future errors beforehand by making sure that it matches that type otherwise warns you in script analysis

also how would the module loader be used and how it would function so i would know easier how to improve it

post this on Help and Feedback > Code Review so more people will see what this topic is intended for