{"id":20476389,"url":"https://github.com/thoughtspile/type2type","last_synced_at":"2025-10-08T19:05:43.123Z","repository":{"id":181801278,"uuid":"667361288","full_name":"thoughtspile/type2type","owner":"thoughtspile","description":"Data structures in TypeScript type system. A Map that maps types to types! Types are in the trees!","archived":false,"fork":false,"pushed_at":"2023-07-17T11:57:10.000Z","size":13,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-04T23:33:30.357Z","etag":null,"topics":["static-typing","type-system","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/thoughtspile/type2type","language":"TypeScript","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/thoughtspile.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":"2023-07-17T10:29:47.000Z","updated_at":"2023-08-12T09:39:32.000Z","dependencies_parsed_at":"2024-09-29T06:16:20.329Z","dependency_job_id":null,"html_url":"https://github.com/thoughtspile/type2type","commit_stats":null,"previous_names":["thoughtspile/typetype"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thoughtspile/type2type","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtspile%2Ftype2type","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtspile%2Ftype2type/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtspile%2Ftype2type/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtspile%2Ftype2type/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thoughtspile","download_url":"https://codeload.github.com/thoughtspile/type2type/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtspile%2Ftype2type/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278955127,"owners_count":26075220,"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","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["static-typing","type-system","typescript"],"created_at":"2024-11-15T15:20:22.578Z","updated_at":"2025-10-08T19:05:43.106Z","avatar_url":"https://github.com/thoughtspile.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## type2type\n\nFour classic data structures implemented in TypeScript type system: Stack, Queue, Set, and Map. No JS, only types. Finally, you can `TStack.push\u003cstack, number\u003e` or `TMap\u003c[[string, 'string']]\u003e`  Good (type-only) unit test coverage. Why?\n\n- Fun.\n- Explore type-only APIs and limitations of TS.\n- Build even more excessively tricky tools on top of this solid foundation.\n\n## Installation\n\n```sh\nnpm i type2type\n```\n\n## Usage\n\nHere's how you can implement static [parentheses validation](https://leetcode.com/problems/valid-parentheses/) in TS type system uning `type2type`:\n\n```ts\nimport { TMap, TStack } from \"type2type\";\n\n// declare valid open / closing parentheses pairs\ntype Brackets = TMap\u003c[\n  [')', '('],\n  [']', '[']\n]\u003e;\ntype close = TMap.keys\u003cBrackets\u003e[number];\ntype open = TMap.values\u003cBrackets\u003e[number];\n\ntype IsValidBrackets\u003cseq extends string, stack extends TStack\u003cunknown[]\u003e = TStack\u003e = \n  // if we see an opening bracket...\n  seq extends `${infer s extends open}${infer tail}`\n    // recurse, recording the bracket type\n    ? IsValidBrackets\u003ctail, TStack.push\u003cstack, s\u003e\u003e\n  // if we see a closing bracket...\n  : seq extends `${infer s extends close}${infer tail}`\n    // if it matches the expect\n    ? (TMap.get\u003cBrackets, s\u003e extends TStack.peek\u003cstack\u003e\n      // recurse\n      ? IsValidBrackets\u003ctail, TStack.pop\u003cstack\u003e\u003e\n      // fail\n      : never)\n  // if we see a non-bracket symbol...\n  : seq extends `${infer _first}${infer tail}`\n    // recurse on rest\n    ? IsValidBrackets\u003ctail, stack\u003e\n  // if seq is empty, ensure we have no unmatched brackets\n  : TStack.empty\u003cstack\u003e;\n\n// Use it as a generic type:\ntype Valid1 = IsValidBrackets\u003c'2 * (1 + (5 + 1))'\u003e\n  // ^? true\ntype Valid2 = IsValidBrackets\u003c'2 * (1 + [5 + 1])'\u003e\n  // ^? true\ntype Wrong1 = IsValidBrackets\u003c'2 * ((1 + (5 + 1))'\u003e\n  // ^? never\ntype Wrong2 = IsValidBrackets\u003c'2 * (1 + [5 + 1))'\u003e\n  // ^? never\n```\n\nTo apply this validation outside type system, declare a function return type based on the generic:\n\n```ts\nfunction calculate\u003cExpr extends string\u003e(expr: Expr): true extends IsValidBrackets\u003cExpr\u003e ? number : never {\n  return 9 as any;\n}\n\nconst x = calculate('2 * (1 + (5 + 1))');\n  // ^? number\nconst y = calculate('2 * (1 + [5 + 1])');\n  // ^? number\nconst err1 = calculate('2 * ((1 + (5 + 1))');\n  // ^? never\nconst err2 = calculate('2 * (1 + [5 + 1))');\n  // ^? never\n```\n\n## API\n\nGeneral principles:\n\n```ts\n// All types are named TType to avoid confusion with JS builtin\nimport { TStack, TQueue, TSet, TMap } from 'type2type'\n\n// Types create an empty collection when called with no parameters\ntype EmptyStack = TStack\n// Types accept a tuple initializer\ntype NumQueue = TQueue\u003c[1, 2, 3]\u003e\n\n// Type \"methods\" are called via TType.method\ntype TrueSet = TStack.push\u003c\n  // The first generic parameter is the TType instance\n  TStack,\n  1\n\u003e\n\n// Of course, data structures are immutable\ntype Effect = TStack.push\u003cEmptyStack, 1\u003e\ntype T = TStack.empty\u003cEmptyStack\u003e\n  // ^? true\n\n// Type has a \"size\" method\ntype Three = TQueue.size\u003cNumQueue\u003e\n// And \"empty\" method\ntype Yes = TMap.empty\u003cTMap\u003e\n  // ^? returns \"true\" if true\ntype No = TQueue.empty\u003cNumQueue\u003e\n  // ^? returns \"never\" if false\n```\n\n### TStack\n\nType stack has three extra methods:\n\n```ts\ntype Empty = TStack\n\n// push adds an element:\ntype OneStack = TStack.push\u003cEmpty, 1\u003e\ntype TwoStack = TStack.push\u003cOneStack, 2\u003e\n\n// peek shows the last added element\ntype Two = TStack.peek\u003cTwoStack\u003e\n// or \"never\" for an empty stack\ntype e = TStack.peek\u003cEmpty\u003e\n\n// pop removes the last added element:\ntype EmptyAgain = TStack.pop\u003cOneStack\u003e\n```\n\n### TQueue\n\nSame as `TStack`, but in FIFO order:\n\n```ts\ntype Empty = TStack\ntype OneStack = TStack.push\u003cEmpty, 1\u003e\ntype TwoStack = TStack.push\u003cOneStack, 2\u003e\n\n// peek shows the first added element\ntype One = TStack.peek\u003cTwoStack\u003e\n  // ^? 1\n```\n\n### TSet\n\nTSet is a set of types. It has three set methods from ES:\n\n```ts\ntype EmptySet = TSet;\n\n// add\ntype Set1 = TSet.add\u003cEmptySet, 1\u003e;\ntype Set2 = TSet.add\u003cSet1, 'hello'\u003e;\n\n// remove\ntype HelloSet = TSet.remove\u003cSet2, 1\u003e;\n\n// has\ntype HasHello = TSet.has\u003cHelloSet, 'hello'\u003e;\n  // ^? true\ntype HasBye = TSet.has\u003cHelloSet, 'bye'\u003e;\n  // ^? never\n```\n\nThree binary set operations:\n\n```ts\ntype Set12 = TSet.union\u003cTSet\u003c[1]\u003e, TSet\u003c[2]\u003e\u003e;\ntype Set2 = TSet.intersection\u003cTSet\u003c[1, 2]\u003e, TSet\u003c[2, 3]\u003e\u003e;\ntype Set1 = TSet.difference\u003cTSet\u003c[1, 2]\u003e, TSet\u003c[2, 3]\u003e\u003e;\n```\n\nA neat `select` method that picks all elements of a certain type:\n\n```ts\ntype SetMixed = TSet\u003c[1, 'hello', 3, true]\u003e\ntype SetNumbers = TSet.select\u003cSetMixed, number\u003e;\n  // ^? TSet\u003c[1, 3]\u003e\n```\n\nAnd `asUnionType`:\n\n```ts\ntype SetMixed = TSet\u003c[1, 'hello', 3, number]\u003e\ntype Allowed = TSet.asUnionType\u003cSetMixed\u003e;\n  // ^? 'hello' | number\n```\n\nNB: union type might look like a set of types, but it actually describes a set of possible values. Union items swallow each other by subtype: `1 | number -\u003e number`, while `TSet\u003c[1, number]\u003e` will always preserve types as is.\n\n### TMap\n\nTMap maps a \"key-type\" to a \"value-type\". It has four usual map operations:\n\n```ts\n// set\ntype map1 = TMap.set\u003cTMap, 1, number\u003e;\n// get\ntype Num = TMap.get\u003cmap1, 1\u003e;\n// has\ntype Yep = TMap.has\u003cmap1, 1\u003e;\ntype Nope = TMap.has\u003cmap1, 2\u003e;\n// remove\ntype EmptyAgain = TMap.remove\u003cmap1, 1\u003e;\n```\n\nSimilar to `TSet`, you get a `select` method which can match key or value types:\n\n```ts\ntype MixedMap = TMap\u003c[[1, number], [2, number], ['hello', string]]\u003e;\n// select by key\ntype SetNumbers = TSet.select\u003cMixedMap, number\u003e;\n  // ^? TMap\u003c[[1, number], [2, number]]\u003e\n// select by value\ntype SetNumbers = TSet.select\u003cMixedMap, unknown, string\u003e;\n  // ^? TMap\u003c[['hello', string]]\u003e\n// select by both\ntype SetNumbers = TSet.select\u003cMixedMap, number, string\u003e;\n  // ^? TMap\u003c[]\u003e\n```\n\nAnd `keys` / `values` that extract one map component as a tuple:\n\n```ts\ntype MixedMap = TMap\u003c[[1, number], [2, number], ['hello', string]]\u003e;\ntype MixedKeys = TMap.keys\u003cMixedMap\u003e;\n  // ^? [1, 2, 'hello']\ntype MixedValues = TMap.values\u003cMixedMap\u003e;\n  // ^? [number, number, string]\n// converts to union type:\ntype PossibleValues = TMap.values\u003cMixedMap\u003e[number];\n  // ^? number | string\n```\n\n---\n\nBuilt in 2023 by [Vladimir Klepov](https://blog.thoughtspile.tech)\n\nSpecial thanks to:\n\n- [expect-type](https://github.com/mmkal/expect-type) for awesome type-only test utils.\n- [nano-staged](https://github.com/usmanyunusov/nano-staged) for quick prettier integration.\n\n[MIT License](./LICENSE) \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtspile%2Ftype2type","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthoughtspile%2Ftype2type","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtspile%2Ftype2type/lists"}