{"id":13837795,"url":"https://github.com/osyrisrblx/t","last_synced_at":"2025-04-04T19:10:13.486Z","repository":{"id":34194260,"uuid":"138662785","full_name":"osyrisrblx/t","owner":"osyrisrblx","description":"A Runtime Typechecker for Roblox","archived":false,"fork":false,"pushed_at":"2024-02-04T05:05:14.000Z","size":219,"stargazers_count":183,"open_issues_count":3,"forks_count":36,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-05-02T21:32:29.301Z","etag":null,"topics":["lua","roblox"],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osyrisrblx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-26T00:07:56.000Z","updated_at":"2024-06-18T15:24:46.082Z","dependencies_parsed_at":"2024-06-18T15:24:42.189Z","dependency_job_id":"3c740f2f-c28f-412c-bced-c071242ace79","html_url":"https://github.com/osyrisrblx/t","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osyrisrblx%2Ft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osyrisrblx%2Ft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osyrisrblx%2Ft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osyrisrblx%2Ft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osyrisrblx","download_url":"https://codeload.github.com/osyrisrblx/t/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247234921,"owners_count":20905854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lua","roblox"],"created_at":"2024-08-04T15:01:25.767Z","updated_at":"2025-04-04T19:10:13.454Z","avatar_url":"https://github.com/osyrisrblx.png","language":"Lua","funding_links":[],"categories":["Libraries","Lua"],"sub_categories":["Utility"],"readme":"\u003ch1 align=\"center\"\u003et\u003c/h1\u003e\n\u003cdiv align=\"center\"\u003e\n\t\u003ca href=\"https://github.com/osyrisrblx/t/actions\"\u003e\n\t\t\u003cimg src=\"https://github.com/osyrisrblx/t/workflows/CI/badge.svg\" alt=\"CI Status\" /\u003e\n\t\u003c/a\u003e\n\t\u003ca href='https://coveralls.io/github/osyrisrblx/t?branch=master'\u003e\n\t\t\u003cimg src='https://coveralls.io/repos/github/osyrisrblx/t/badge.svg?branch=master' alt='Coverage Status' /\u003e\n\t\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\tA Runtime Type Checker for Roblox\n\u003c/div\u003e\n\n\u003cdiv\u003e\u0026nbsp;\u003c/div\u003e\n\nt is a module which allows you to create type definitions to check values against.\n\n## Download\n[You can download the latest copy of t here.](https://raw.githubusercontent.com/osyrisrblx/t/master/lib/init.lua)\n\n## Why?\nWhen building large systems, it can often be difficult to find type mismatch bugs.\\\nTypechecking helps you ensure that your functions are recieving the appropriate types for their arguments.\n\nIn Roblox specifically, it is important to type check your Remote objects to ensure that exploiters aren't sending you bad data which can cause your server to error (and potentially crash!).\n\n## Crash Course\n```Lua\nlocal t = require(path.to.t)\n\nlocal fooCheck = t.tuple(t.string, t.number, t.optional(t.string))\nlocal function foo(a, b, c)\n\tassert(fooCheck(a, b, c))\n\t-- you can now assume:\n\t--\ta is a string\n\t--\tb is a number\n\t--\tc is either a string or nil\nend\n\nfoo() --\u003e Error: Bad tuple index #1: string expected, got nil\nfoo(\"1\", 2)\nfoo(\"1\", 2, \"3\")\nfoo(\"1\", 2, 3) --\u003e Error: Bad tuple index #3: (optional) string expected, got number\n```\n\nCheck out src/t.spec.lua for a variety of good examples!\n\n## Primitives\n|Type     |  |Member     |\n|---------|--|-----------|\n|boolean  |=\u003e|t.boolean  |\n|thread   |=\u003e|t.thread   |\n|function |=\u003e|t.callback |\n|nil      |=\u003e|t.none     |\n|number   |=\u003e|t.number   |\n|string   |=\u003e|t.string   |\n|table    |=\u003e|t.table    |\n|userdata |=\u003e|t.userdata |\n\nAny primitive can be checked with a built-in primitive function.\\\nPrimitives are found under the same name as their type name except for two:\n- nil -\u003e t.none\n- function -\u003e t.callback\n\nThese two are renamed due to Lua restrictions on reserved words.\n\nAll Roblox primitives are also available and can be found under their respective type names.\\\nWe won't list them here to due how many there are, but as an example you can access a few like this:\n```Lua\nt.Instance\nt.CFrame\nt.Color3\nt.Vector3\n-- etc...\n```\n\nYou can check values against these primitives like this:\n```Lua\nlocal x = 1\nprint(t.number(x)) --\u003e true\nprint(t.string(x)) --\u003e false, \"string expected, got number\"\n```\n\n## Type Composition\nOften, you can combine types to create a composition of types.\\\nFor example:\n```Lua\nlocal mightBeAString = t.optional(t.string)\nprint(mightBeAString(\"Hello\")) --\u003e true\nprint(mightBeAString()) --\u003e true\nprint(mightBeAString(1)) --\u003e false, \"(optional) string expected, got number\"\n```\n\nThese get denoted as function calls below with specified arguments. `check` can be any other type checker.\n\n## Meta Type Functions\nThe real power of t is in the meta type functions.\n\n**`t.any`**\\\nPasses if value is non-nil.\n\n**`t.literal(...)`**\\\nPasses if value matches any given value exactly.\n\n**`t.keyOf(keyTable)`**\\\nReturns a t.union of each key in the table as a t.literal\n\n**`t.valueOf(valueTable)`**\\\nReturns a t.union of each value in the table as a t.literal\n\n**`t.optional(check)`**\\\nPasses if value is either nil or passes `check`\n\n**`t.tuple(...)`**\\\nYou can define a tuple type with `t.tuple(...)`.\\\nThe arguments should be a list of type checkers.\n\n**`t.union(...)`** - ( alias: `t.some(...)` )\\\nYou can define a union type with `t.union(...)`.\\\nThe arguments should be a list of type checkers.\\\n**At least one check must pass**\\\ni.e. `t.union(a, b, c)` -\u003e `a OR b OR c`\n\n**`t.intersection(...)`** - ( alias: `t.every(...)` )\\\nYou can define an intersection type with `t.intersection(...)`.\\\nThe arguments should be a list of type checkers.\\\n**All checks must pass**\\\ni.e. `t.intersection(a, b, c)` -\u003e `a AND b AND c`\n\n**`t.keys(check)`**\\\nMatches a table's keys against `check`\n\n**`t.values(check)`**\\\nMatches a table's values against `check`\n\n**`t.map(keyCheck, valueCheck)`**\\\nChecks all of a table's keys against `keyCheck` and all of a table's values against `valueCheck`\n\nThere's also type checks for arrays and interfaces but we'll cover those in their own sections!\n\n## Special Number Functions\n\nt includes a few special functions for checking numbers, these can be useful to ensure the given value is within a certain range.\n\n**General:**\\\n**`t.nan`**\\\ndetermines if value is `NaN`\\\nAll of the following checks will not pass for `NaN` values.\\\nIf you need to allow for `NaN`, use `t.union(t.number, t.nan)`\n\n**`t.integer`**\\\nchecks `t.number` and determines if value is an integer\n\n**`t.numberPositive`**\\\nchecks `t.number` and determines if the value \u003e 0\n\n**`t.numberNegative`**\\\nchecks `t.number` and determines if the value \u003c 0\n\n**Inclusive  Comparisons:**\\\n**`t.numberMin(min)`**\\\nchecks `t.number` and determines if value \u003e= min\n\n**`t.numberMax(max)`**\\\nchecks `t.number` and determines if value \u003c= max\n\n**`t.numberConstrained(min, max)`**\\\nchecks `t.number` and determines if min \u003c= value \u003c= max\n\n**Exclusive Comparisons:**\\\n**`t.numberMinExclusive(min)`**\\\nchecks `t.number` and determines if value \u003e min\n\n**`t.numberMaxExclusive(max)`**\\\nchecks `t.number` and determines if value \u003c max\n\n**`t.numberConstrainedExclusive(min, max)`**\\\nchecks `t.number` and determines if min \u003c value \u003c max\n\n## Special String Functions\n\nt includes a few special functions for checking strings\n\n**`t.match(pattern)`**\\\nchecks `t.string` and determines if value matches the pattern via `string.match(value, pattern)`\n\n## Arrays\nIn Lua, arrays are a special type of table where all the keys are sequential integers.\\\nt has special functions for checking against arrays.\n\n**`t.array(check)`**\\\ndetermines that the value is a table and all of it's keys are sequential integers and ensures all of the values in the table match `check`\n\n## Interfaces\nInterfaces can be defined through `t.interface(definition)` where `definition` is a table of type checkers.\\\nFor example:\n```Lua\nlocal IPlayer = t.interface({\n\tName = t.string,\n\tScore = t.number,\n})\n\nlocal myPlayer = { Name = \"TestPlayer\", Score = 100 }\nprint(IPlayer(myPlayer)) --\u003e true\nprint(IPlayer({})) --\u003e false, \"[interface] bad value for Name: string expected, got nil\"\n```\n\nYou can use `t.optional(check)` to make an interface field optional or `t.union(...)` if a field can be multiple types.\n\nYou can even put interfaces inside interfaces!\n```Lua\nlocal IPlayer = t.interface({\n\tName = t.string,\n\tScore = t.number,\n\tInventory = t.interface({\n\t\tSize = t.number\n\t})\n})\n\nlocal myPlayer = {\n\tName = \"TestPlayer\",\n\tScore = 100,\n\tInventory = {\n\t\tSize = 20\n\t}\n}\nprint(IPlayer(myPlayer)) --\u003e true\n```\n\nIf you want to make sure an value _exactly_ matches a given interface (no extra fields),\\\nyou can use `t.strictInterface(definition)` where `definition` is a table of type checkers.\\\nFor example:\n```Lua\nlocal IPlayer = t.strictInterface({\n\tName = t.string,\n\tScore = t.number,\n})\n\nlocal myPlayer1 = { Name = \"TestPlayer\", Score = 100 }\nlocal myPlayer2 = { Name = \"TestPlayer\", Score = 100, A = 1 }\nprint(IPlayer(myPlayer1)) --\u003e true\nprint(IPlayer(myPlayer2)) --\u003e false, \"[interface] unexpected field 'A'\"\n```\n\n## Roblox Instances\nt includes two functions to check the types of Roblox Instances.\n\n**`t.instanceOf(className[, childTable])`**\\\nensures the value is an Instance and it's ClassName exactly matches `className`\\\nIf you provide a `childTable`, it will be automatically passed to `t.children()`\n\n**`t.instanceIsA(className[, childTable])`**\\\nensures the value is an Instance and it's ClassName matches `className` by a IsA comparison. ([see here](http://wiki.roblox.com/index.php?title=API:Class/Instance/FindFirstAncestorWhichIsA))\n\n**`t.children(checkTable)`**\\\nTakes a table where keys are child names and values are functions to check the children against.\\\nPass an instance tree into the function.\n\n**Warning! If you pass in a tree with more than one child of the same name, this function will always return false**\n\n## Roblox Enums\n\nt allows type checking for Roblox Enums!\n\n**`t.Enum`**\\\nEnsures the value is an Enum, i.e. `Enum.Material`.\n\n**`t.EnumItem`**\\\nEnsures the value is an EnumItem, i.e. `Enum.Material.Plastic`.\n\nbut the real power here is:\n\n**`t.enum(enum)`**\\\nThis will pass if value is an EnumItem which belongs to `enum`.\n\n## Function Wrapping\nHere's a common pattern people use when working with t:\n```Lua\nlocal fooCheck = t.tuple(t.string, t.number, t.optional(t.string))\nlocal function foo(a, b, c)\n\tassert(fooCheck(a, b, c))\n\t-- function now assumes a, b, c are valid\nend\n```\n\n**`t.wrap(callback, argCheck)`**\\\n`t.wrap(callback, argCheck)` allows you to shorten this to the following:\n```Lua\nlocal fooCheck = t.tuple(t.string, t.number, t.optional(t.string))\nlocal foo = t.wrap(function(a, b, c)\n\t-- function now assumes a, b, c are valid\nend, fooCheck)\n```\n\nOR\n\n```Lua\nlocal foo = t.wrap(function(a, b, c)\n\t-- function now assumes a, b, c are valid\nend, t.tuple(t.string, t.number, t.optional(t.string)))\n```\n\nAlternatively, there's also:\n**`t.strict(check)`**\\\nwrap your whole type in `t.strict(check)` and it will run an `assert` on calls.\\\nThe example from above could alternatively look like:\n```Lua\nlocal fooCheck = t.strict(t.tuple(t.string, t.number, t.optional(t.string)))\nlocal function foo(a, b, c)\n\tfooCheck(a, b, c)\n\t-- function now assumes a, b, c are valid\nend\n```\n\n## Tips and Tricks\nYou can create your own type checkers with a simple function that returns a boolean.\\\nThese custom type checkers fit perfectly with the rest of t's functions.\n\nIf you roll your own custom OOP framework, you can easily integrate t with a custom type checker.\\\nFor example:\n```Lua\nlocal MyClass = {}\nMyClass.__index = MyClass\n\nfunction MyClass.new()\n\tlocal self = setmetatable({}, MyClass)\n\t-- setup instance\n\treturn self\nend\n\nlocal function instanceOfClass(class)\n\treturn function(value)\n\t\tlocal tableSuccess, tableErrMsg = t.table(value)\n\t\tif not tableSuccess then\n\t\t\treturn false, tableErrMsg or \"\" -- pass error message for value not being a table\n\t\tend\n\n\t\tlocal mt = getmetatable(value)\n\t\tif not mt or mt.__index ~= class then\n\t\t\treturn false, \"bad member of class\" -- custom error message\n\t\tend\n\n\t\treturn true -- all checks passed\n\tend\nend\n\nlocal instanceOfMyClass = instanceOfClass(MyClass)\n\nlocal myObject = MyClass.new()\nprint(instanceOfMyClass(myObject)) --\u003e true\n```\n\n## Known Issues\n\nYou can put a `t.tuple(...)` inside an array or interface, but that doesn't really make any sense..\\\nIn the future, this may error.\n\n## Notes\nThis library was heavily inspired by [io-ts](https://github.com/gcanti/io-ts), a fantastic runtime type validation library for TypeScript.\n\n## Why did you name it t?\nThe whole idea is that most people import modules via:\\\n`local X = require(path.to.X)`\\\nSo whatever I name the library will be what people name the variable.\\\nIf I made the name of the library longer, the type definitions become more noisy / less readable.\\\nThings like this are pretty common:\\\n`local fooCheck = t.tuple(t.string, t.number, t.optional(t.string))`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosyrisrblx%2Ft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosyrisrblx%2Ft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosyrisrblx%2Ft/lists"}