{"id":"mrkirdid/unithub","name":"unithub","scope":"mrkirdid","platform":"roblox","description":"Typed service/controller lifecycle bootstrapper with stable proxies, replicated client manifests, and bounded preload.","version":"0.2.14","latest":"0.2.14","versions":["0.2.1","0.2.11","0.2.12","0.2.13","0.2.14"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{"howmanysmall/janitor":{"version":"^1.18.3","alias":"Janitor"},"evaera/promise":{"version":"^4.0.0","alias":"Promise"},"lucasmzreal/fastsignal":{"version":"^10.4.0","alias":"Signal"}},"integrity":"a4ec52cbe37dbedf554a051a0640a1a13e008242f0ec053f90d6ebe29e61f0bc","likes":0,"downloads":0,"install":"forest install mrkirdid/unithub","url":"https://forest.dev/p/roblox/mrkirdid/unithub","files":"https://api.forest.dev/ai/package/roblox/mrkirdid/unithub/files","readme":"# Unithub\n\nUnithub is a small Roblox service/controller bootstrapper for Wally projects. The server owns the real setup and replicates a lightweight manifest so the client can start with only `Unithub:Init()`.\n\n## Install\n\n```toml\n[dependencies]\nUnithub = \"mrkirdid/unithub@^0.2.0\"\n```\n\n## Server Bootstrap\n\n```luau\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal ServerScriptService = game:GetService(\"ServerScriptService\")\n\nlocal Unithub = require(ReplicatedStorage.Packages.Unithub)\n\nUnithub:Init({\n\tServices = {\n\t\tServerScriptService.Server.Services,\n\t},\n\n\tControllers = {\n\t\tReplicatedStorage.Client.Controllers,\n\t},\n\n\tPreload = true,\n\tTimeout = 10,\n})\n```\n\n## Client Bootstrap\n\n```luau\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\n\nlocal Unithub = require(ReplicatedStorage.Packages.Unithub)\n\nUnithub:Init()\n```\n\nClient `Init` waits for the server manifest and only discovers controllers after every startup service has successfully completed its service lifecycle. If the server reports service failures or does not report readiness within the configured lifecycle budget, client init errors instead of loading controllers early.\n\nOptional client exclusions are local-only:\n\n```luau\nUnithub:Init({\n\tExcludedUnits = { \"PVP\" },\n})\n```\n\n## Controller And Service Modules\n\nOnly `ModuleScript` descendants whose direct parent is a `Folder` are discovered. This lets a service or controller module have helper ModuleScripts as children without those helpers being auto-loaded.\n\n```luau\nreturn {\n\tName = \"Inventory\",\n\tPriority = 10,\n\n\tInit = function(self, hub)\n\t\tself.Data = hub:GetService(\"Data\")\n\tend,\n\n\tStart = function(self, hub)\n\t\tprint(\"Inventory ready\")\n\tend,\n}\n```\n\nLegacy names still work:\n\n- `Prepare` is an alias for `Init`.\n- `Activate` is an alias for `Start`.\n- `GetUnit` is an alias for `Get`.\n- `EngageUnits` is an alias for `Start`.\n\n## Proxies And State\n\n`GetService`, `GetController`, and `Get` return stable proxies. Proxies exist before modules are required, so circular references through Unithub do not need heartbeat polling.\n\n```luau\nlocal Data = Unithub:GetService(\"Data\")\nlocal Inventory = Unithub:WaitForController(\"Inventory\", 5)\n\nif Unithub:CheckService(\"Data\") then\n\tprint(Unithub:GetStatus(\"Data\").State)\nend\n```\n\n## Runtime Folders\n\nFolders are watched by default. If a valid module is added while the game is running, Unithub registers it and advances it through the current lifecycle phase.\n\n```luau\nUnithub:AddServiceFolder(ServerScriptService.Server.Services)\nUnithub:AddControllerFolder(ReplicatedStorage.Client.Controllers)\nUnithub:AddFolder(ReplicatedStorage.Shared.SharedControllers, {\n\tKind = \"Controller\",\n})\n```\n\n## Parallel Luau\n\nUnithub can create a real Parallel Luau dispatcher backed by Roblox `Actor` workers. The dispatcher runs job ModuleScript methods in parallel and resolves a Promise when each job returns.\n\n```luau\nlocal dispatcher = Unithub:CreateDispatcher({\n\tWorkerCount = 4,\n\tTimeout = 5,\n})\n\ndispatcher:Run(ReplicatedStorage.Shared.Jobs.SumNumbers, {\n\tA = 10,\n\tB = 32,\n}):andThen(function(result)\n\tprint(result)\nend)\n```\n\nJob modules are required inside the worker Actor. Export plain functions that only use parallel-safe APIs while they are running in parallel:\n\n```luau\nreturn {\n\tRun = function(payload, context)\n\t\treturn payload.A + payload.B\n\tend,\n\n\tRaycast = function(payload, context)\n\t\tlocal result = workspace:Raycast(payload.Origin, payload.Direction, payload.Params)\n\n\t\tcontext.Synchronize()\n\t\t-- Do unsafe DataModel writes here, then call context.Desynchronize() before more parallel work.\n\n\t\treturn result\n\tend,\n}\n```\n\nConvenience calls use the default dispatcher:\n\n```luau\nUnithub:RunParallel(ReplicatedStorage.Shared.Jobs.SumNumbers, { A = 1, B = 2 })\nUnithub:DispatchParallel(ReplicatedStorage.Shared.Jobs.SumNumbers, \"Run\", { A = 1, B = 2 })\nUnithub:MapParallel(ReplicatedStorage.Shared.Jobs.SumNumbers, \"Run\", {\n\t{ A = 1, B = 2 },\n\t{ A = 3, B = 4 },\n})\n```\n\nRaw Luau closures cannot be sent to another Actor VM, so parallel jobs must live in ModuleScripts. Payloads and return values must be safe to send through Roblox Actor/BindableEvent messaging. Timed-out work rejects its Promise, but Roblox does not kill the running Actor callback; that worker becomes available again when the late result returns or when the dispatcher is destroyed. Use `Unithub:GetSharedTable(name, initial?)` or `context.GetSharedTable(name, initial?)` for larger shared state.\n\n## Preload\n\n`Preload = true` aggressively preloads the currently replicated client game with hard time caps. It collects content from `ReplicatedFirst`, `ReplicatedStorage`, `StarterGui`, `StarterPlayer`, current `Workspace` descendants, controller folders, tags, and explicit manifests.\n\nPreload covers images, decals, textures, sounds, animations, meshes, particle textures, beam/trail textures, surface appearances, skyboxes, and video frames. It uses `ContentProvider:PreloadAsync()` callback progress, chunks requests, warms important UI images, records failures, and continues in the background instead of trapping players forever.\n\n```luau\nUnithub:Init({\n\tPreload = {\n\t\tAggressive = true,\n\t\tCriticalTimeout = 20,\n\t\tChunkSize = 125,\n\t\tCriticalTags = { \"PreloadCritical\" },\n\t\tIgnoreTags = { \"PreloadIgnore\" },\n\t\tExplicitCriticalAssets = {\n\t\t\t\"rbxassetid://123456789\",\n\t\t},\n\t},\n})\n```\n","readmeTruncated":false}