Files
Next_Station_Plugin/include/RuntimeLib.lua
2025-09-14 13:34:16 +02:00

261 lines
5.9 KiB
Lua

local Promise = require(script.Parent.Promise)
local RunService = game:GetService("RunService")
local OUTPUT_PREFIX = "roblox-ts: "
local NODE_MODULES = "node_modules"
local DEFAULT_SCOPE = "@rbxts"
local TS = {}
TS.Promise = Promise
local function isPlugin(context)
return RunService:IsStudio() and context:FindFirstAncestorWhichIsA("Plugin") ~= nil
end
function TS.getModule(context, scope, moduleName)
-- legacy call signature
if moduleName == nil then
moduleName = scope
scope = DEFAULT_SCOPE
end
-- ensure modules have fully replicated
if RunService:IsRunning() and RunService:IsClient() and not isPlugin(context) and not game:IsLoaded() then
game.Loaded:Wait()
end
local object = context
repeat
local nodeModulesFolder = object:FindFirstChild(NODE_MODULES)
if nodeModulesFolder then
local scopeFolder = nodeModulesFolder:FindFirstChild(scope)
if scopeFolder then
local module = scopeFolder:FindFirstChild(moduleName)
if module then
return module
end
end
end
object = object.Parent
until object == nil
error(OUTPUT_PREFIX .. "Could not find module: " .. moduleName, 2)
end
-- This is a hash which TS.import uses as a kind of linked-list-like history of [Script who Loaded] -> Library
local currentlyLoading = {}
local registeredLibraries = {}
function TS.import(context, module, ...)
for i = 1, select("#", ...) do
module = module:WaitForChild((select(i, ...)))
end
if module.ClassName ~= "ModuleScript" then
error(OUTPUT_PREFIX .. "Failed to import! Expected ModuleScript, got " .. module.ClassName, 2)
end
currentlyLoading[context] = module
-- Check to see if a case like this occurs:
-- module -> Module1 -> Module2 -> module
-- WHERE currentlyLoading[module] is Module1
-- and currentlyLoading[Module1] is Module2
-- and currentlyLoading[Module2] is module
local currentModule = module
local depth = 0
while currentModule do
depth = depth + 1
currentModule = currentlyLoading[currentModule]
if currentModule == module then
local str = currentModule.Name -- Get the string traceback
for _ = 1, depth do
currentModule = currentlyLoading[currentModule]
str = str .. "" .. currentModule.Name
end
error(OUTPUT_PREFIX .. "Failed to import! Detected a circular dependency chain: " .. str, 2)
end
end
if not registeredLibraries[module] then
if _G[module] then
error(
OUTPUT_PREFIX
.. "Invalid module access! Do you have multiple TS runtimes trying to import this? "
.. module:GetFullName(),
2
)
end
_G[module] = TS
registeredLibraries[module] = true -- register as already loaded for subsequent calls
end
local data = require(module)
if currentlyLoading[context] == module then -- Thread-safe cleanup!
currentlyLoading[context] = nil
end
return data
end
function TS.instanceof(obj, class)
-- custom Class.instanceof() check
if type(class) == "table" and type(class.instanceof) == "function" then
return class.instanceof(obj)
end
-- metatable check
if type(obj) == "table" then
obj = getmetatable(obj)
while obj ~= nil do
if obj == class then
return true
end
local mt = getmetatable(obj)
if mt then
obj = mt.__index
else
obj = nil
end
end
end
return false
end
function TS.async(callback)
return function(...)
local n = select("#", ...)
local args = { ... }
return Promise.new(function(resolve, reject)
coroutine.wrap(function()
local ok, result = pcall(callback, unpack(args, 1, n))
if ok then
resolve(result)
else
reject(result)
end
end)()
end)
end
end
function TS.await(promise)
if not Promise.is(promise) then
return promise
end
local status, value = promise:awaitStatus()
if status == Promise.Status.Resolved then
return value
elseif status == Promise.Status.Rejected then
error(value, 2)
else
error("The awaited Promise was cancelled", 2)
end
end
local SIGN = 2 ^ 31
local COMPLEMENT = 2 ^ 32
local function bit_sign(num)
-- Restores the sign after an unsigned conversion according to 2s complement.
if bit32.btest(num, SIGN) then
return num - COMPLEMENT
else
return num
end
end
function TS.bit_lrsh(a, b)
return bit_sign(bit32.arshift(a, b))
end
TS.TRY_RETURN = 1
TS.TRY_BREAK = 2
TS.TRY_CONTINUE = 3
function TS.try(try, catch, finally)
-- execute try
local trySuccess, exitTypeOrTryError, returns = pcall(try)
local exitType, tryError
if trySuccess then
exitType = exitTypeOrTryError
else
tryError = exitTypeOrTryError
end
local catchSuccess = true
local catchError
-- if try block failed, and catch block exists, execute catch
if not trySuccess and catch then
local newExitTypeOrCatchError, newReturns
catchSuccess, newExitTypeOrCatchError, newReturns = pcall(catch, tryError)
local newExitType
if catchSuccess then
newExitType = newExitTypeOrCatchError
else
catchError = newExitTypeOrCatchError
end
if newExitType then
exitType, returns = newExitType, newReturns
end
end
-- execute finally
if finally then
local newExitType, newReturns = finally()
if newExitType then
exitType, returns = newExitType, newReturns
end
end
-- if exit type is a control flow, do not rethrow errors
if exitType ~= TS.TRY_RETURN and exitType ~= TS.TRY_BREAK and exitType ~= TS.TRY_CONTINUE then
-- if catch block threw an error, rethrow it
if not catchSuccess then
error(catchError, 2)
end
-- if try block threw an error and there was no catch block, rethrow it
if not trySuccess and not catch then
error(tryError, 2)
end
end
return exitType, returns
end
function TS.generator(callback)
local co = coroutine.create(callback)
return {
next = function(...)
if coroutine.status(co) == "dead" then
return { done = true }
else
local success, value = coroutine.resume(co, ...)
if success == false then
error(value, 2)
end
return {
value = value,
done = coroutine.status(co) == "dead",
}
end
end,
}
end
return TS