{"id":"yoheiyayoi/resulty","name":"resulty","scope":"yoheiyayoi","platform":"roblox","description":"⚡ Type-safe error handling for Luau","version":"1.0.1","latest":"1.0.1","versions":["1.0.0","1.0.1"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"e647b529425d7daa8022170e698bbbe26b7416f1785d6ce986d05de0e935d508","likes":0,"downloads":0,"install":"forest install yoheiyayoi/resulty","url":"https://forest.dev/p/roblox/yoheiyayoi/resulty","files":"https://api.forest.dev/ai/package/roblox/yoheiyayoi/resulty/files","readme":"# Resulty\r\n\r\nRust-style Result type for Luau. No more `pcall` spaghetti.\r\n\r\n## Why?\r\n\r\nEver get tired of writing this?\r\n\r\n```luau\r\nlocal ok, result = pcall(function()\r\n    return something:ThatMightFail()\r\nend)\r\n\r\nif ok then\r\n    -- do stuff\r\nelse\r\n    -- handle error\r\nend\r\n```\r\n\r\nYeah, me too. This gives you proper error handling that you can actually chain together.\r\n\r\n## Install\r\n\r\n**Wally:**\r\n```toml\r\n[dependencies]\r\nResulty = \"yohei_yayoi/resulty@1.0.0\"\r\n```\r\n\r\nOr just grab `src/init.luau` and drop it in your project.\r\n\r\n## Quick Example\r\n\r\n```luau\r\nlocal Resulty = require(path.to.Resulty)\r\n\r\nlocal result = Resulty.try(function()\r\n    return somethingRisky()\r\nend)\r\n\r\nresult:match({\r\n    Ok = function(value) print(\"got:\", value) end,\r\n    Err = function(err) warn(\"oops:\", err) end,\r\n})\r\n```\r\n\r\n## API\r\n\r\n### Making Results\r\n\r\n**`Resulty.Ok(value)`** - wrap a success value\r\n```luau\r\nlocal good = Resulty.Ok(\"nice\")\r\n```\r\n\r\n**`Resulty.Err(error)`** - wrap an error\r\n```luau\r\nlocal bad = Resulty.Err(\"something broke\")\r\n```\r\n\r\n**`Resulty.try(fn, ...)`** - pcall but better\r\n```luau\r\nlocal result = Resulty.try(function()\r\n    return workspace:FindFirstChild(\"Part\").Name\r\nend)\r\n```\r\n\r\n**`Resulty.tryWith(context, fn, ...)`** - same as try but adds context to errors\r\n```luau\r\nlocal result = Resulty.tryWith(\"loading data\", function()\r\n    return DataStore:GetAsync(key)\r\nend)\r\n-- if it fails: \"loading data: [actual error]\"\r\n```\r\n\r\n**`Resulty.validate(value, check, errMsg?)`** - validate something\r\n```luau\r\nlocal result = Resulty.validate(age, function(v) return v >= 18 end, \"too young\")\r\n```\r\n\r\n### Combining Results\r\n\r\n**`Resulty.all(results)`** - all must succeed, returns array of values\r\n```luau\r\nlocal all = Resulty.all({\r\n    Resulty.Ok(1),\r\n    Resulty.Ok(2),\r\n})\r\n-- Ok({1, 2})\r\n```\r\n\r\n**`Resulty.any(results)`** - returns first success\r\n```luau\r\nlocal first = Resulty.any({\r\n    Resulty.Err(\"nope\"),\r\n    Resulty.Ok(\"this one\"),\r\n})\r\n```\r\n\r\n### Checking\r\n\r\n**`:isOk()`** / **`:isErr()`** - what it sounds like\r\n\r\n### Getting Values Out\r\n\r\n**`:unwrap()`** - get the value or explode\r\n```luau\r\nlocal val = result:unwrap() -- errors if result is Err!\r\n```\r\n\r\n**`:unwrapOr(default)`** - get the value or use fallback\r\n```luau\r\nlocal name = result:unwrapOr(\"Unknown\")\r\n```\r\n\r\n**`:unwrapOrElse(fn)`** - get the value or compute fallback\r\n```luau\r\nlocal val = result:unwrapOrElse(function(err)\r\n    warn(err)\r\n    return getFallback()\r\nend)\r\n```\r\n\r\n**`:ok()`** / **`:err()`** - returns value/error or nil\r\n\r\n### Pattern Matching\r\n\r\n**`:match(handlers)`** - handle both cases\r\n```luau\r\nresult:match({\r\n    Ok = function(v) return \"got \" .. v end,\r\n    Err = function(e) return \"failed: \" .. e end,\r\n})\r\n```\r\n\r\n### Transforming\r\n\r\n**`:map(fn)`** - transform success value\r\n```luau\r\nResulty.Ok(5):map(function(n) return n * 2 end) -- Ok(10)\r\n```\r\n\r\n**`:mapErr(fn)`** - transform error value\r\n\r\n**`:filter(predicate, errMsg?)`** - keep value if predicate passes\r\n```luau\r\nResulty.Ok(5):filter(function(n) return n % 2 == 0 end, \"not even\")\r\n-- Err(\"not even\")\r\n```\r\n\r\n### Chaining\r\n\r\n**`:andThen(fn)`** - chain operations (fn must return a Result)\r\n```luau\r\nResulty.Ok(userId)\r\n    :andThen(function(id)\r\n        return Resulty.try(function()\r\n            return DataStore:GetAsync(id)\r\n        end)\r\n    end)\r\n```\r\n\r\n**`:orElse(fn)`** - try recovery on error\r\n```luau\r\nfetchPrimary()\r\n    :orElse(function(err)\r\n        warn(\"primary failed:\", err)\r\n        return fetchBackup()\r\n    end)\r\n```\r\n\r\n### Debugging\r\n\r\n**`:inspect(fn)`** - peek at success value (for logging)\r\n```luau\r\nresult:inspect(function(v) print(\"got:\", v) end)\r\n```\r\n\r\n**`:inspectErr(fn)`** - peek at error value\r\n\r\n### Logic\r\n\r\n**`:and_(other)`** - returns other if self is Ok\r\n**`:or_(other)`** - returns other if self is Err\r\n\r\n### Promise-ish\r\n\r\n**`:asPromise()`** - if you want that interface\r\n```luau\r\nresult:asPromise()\r\n    :andThen(function(v) return v * 2 end)\r\n    :catch(function(e) return 0 end)\r\n```\r\n\r\n## Real Example\r\n\r\n```luau\r\nlocal function loadPlayerData(player)\r\n    return Resulty.tryWith(\"loading \" .. player.Name, function()\r\n        return DataStore:GetAsync(player.UserId)\r\n    end)\r\n    :andThen(function(data)\r\n        return Resulty.validate(data, function(d)\r\n            return d ~= nil and d.version ~= nil\r\n        end, \"bad data format\")\r\n    end)\r\n    :map(migrateData)\r\nend\r\n\r\nloadPlayerData(player):match({\r\n    Ok = function(data) applyData(player, data) end,\r\n    Err = function(err)\r\n        warn(err)\r\n        applyDefaults(player)\r\n    end,\r\n})\r\n```\r\n\r\n## License\r\n\r\nMIT\r\n\r\n## Author\r\nyooo_ / YoheiKung (Roblox) / yohei_yayoi (Discord, GitHub)\r\n","readmeTruncated":false}