https://github.com/thoughtspile/type2type
Data structures in TypeScript type system. A Map that maps types to types! Types are in the trees!
https://github.com/thoughtspile/type2type
static-typing type-system typescript
Last synced: 9 months ago
JSON representation
Data structures in TypeScript type system. A Map that maps types to types! Types are in the trees!
- Host: GitHub
- URL: https://github.com/thoughtspile/type2type
- Owner: thoughtspile
- License: mit
- Created: 2023-07-17T10:29:47.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-07-17T11:57:10.000Z (almost 3 years ago)
- Last Synced: 2025-10-04T23:33:30.357Z (9 months ago)
- Topics: static-typing, type-system, typescript
- Language: TypeScript
- Homepage: https://github.com/thoughtspile/type2type
- Size: 12.7 KB
- Stars: 9
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
## type2type
Four classic data structures implemented in TypeScript type system: Stack, Queue, Set, and Map. No JS, only types. Finally, you can `TStack.push` or `TMap<[[string, 'string']]>` Good (type-only) unit test coverage. Why?
- Fun.
- Explore type-only APIs and limitations of TS.
- Build even more excessively tricky tools on top of this solid foundation.
## Installation
```sh
npm i type2type
```
## Usage
Here's how you can implement static [parentheses validation](https://leetcode.com/problems/valid-parentheses/) in TS type system uning `type2type`:
```ts
import { TMap, TStack } from "type2type";
// declare valid open / closing parentheses pairs
type Brackets = TMap<[
[')', '('],
[']', '[']
]>;
type close = TMap.keys[number];
type open = TMap.values[number];
type IsValidBrackets = TStack> =
// if we see an opening bracket...
seq extends `${infer s extends open}${infer tail}`
// recurse, recording the bracket type
? IsValidBrackets>
// if we see a closing bracket...
: seq extends `${infer s extends close}${infer tail}`
// if it matches the expect
? (TMap.get extends TStack.peek
// recurse
? IsValidBrackets>
// fail
: never)
// if we see a non-bracket symbol...
: seq extends `${infer _first}${infer tail}`
// recurse on rest
? IsValidBrackets
// if seq is empty, ensure we have no unmatched brackets
: TStack.empty;
// Use it as a generic type:
type Valid1 = IsValidBrackets<'2 * (1 + (5 + 1))'>
// ^? true
type Valid2 = IsValidBrackets<'2 * (1 + [5 + 1])'>
// ^? true
type Wrong1 = IsValidBrackets<'2 * ((1 + (5 + 1))'>
// ^? never
type Wrong2 = IsValidBrackets<'2 * (1 + [5 + 1))'>
// ^? never
```
To apply this validation outside type system, declare a function return type based on the generic:
```ts
function calculate(expr: Expr): true extends IsValidBrackets ? number : never {
return 9 as any;
}
const x = calculate('2 * (1 + (5 + 1))');
// ^? number
const y = calculate('2 * (1 + [5 + 1])');
// ^? number
const err1 = calculate('2 * ((1 + (5 + 1))');
// ^? never
const err2 = calculate('2 * (1 + [5 + 1))');
// ^? never
```
## API
General principles:
```ts
// All types are named TType to avoid confusion with JS builtin
import { TStack, TQueue, TSet, TMap } from 'type2type'
// Types create an empty collection when called with no parameters
type EmptyStack = TStack
// Types accept a tuple initializer
type NumQueue = TQueue<[1, 2, 3]>
// Type "methods" are called via TType.method
type TrueSet = TStack.push<
// The first generic parameter is the TType instance
TStack,
1
>
// Of course, data structures are immutable
type Effect = TStack.push
type T = TStack.empty
// ^? true
// Type has a "size" method
type Three = TQueue.size
// And "empty" method
type Yes = TMap.empty
// ^? returns "true" if true
type No = TQueue.empty
// ^? returns "never" if false
```
### TStack
Type stack has three extra methods:
```ts
type Empty = TStack
// push adds an element:
type OneStack = TStack.push
type TwoStack = TStack.push
// peek shows the last added element
type Two = TStack.peek
// or "never" for an empty stack
type e = TStack.peek
// pop removes the last added element:
type EmptyAgain = TStack.pop
```
### TQueue
Same as `TStack`, but in FIFO order:
```ts
type Empty = TStack
type OneStack = TStack.push
type TwoStack = TStack.push
// peek shows the first added element
type One = TStack.peek
// ^? 1
```
### TSet
TSet is a set of types. It has three set methods from ES:
```ts
type EmptySet = TSet;
// add
type Set1 = TSet.add;
type Set2 = TSet.add;
// remove
type HelloSet = TSet.remove;
// has
type HasHello = TSet.has;
// ^? true
type HasBye = TSet.has;
// ^? never
```
Three binary set operations:
```ts
type Set12 = TSet.union, TSet<[2]>>;
type Set2 = TSet.intersection, TSet<[2, 3]>>;
type Set1 = TSet.difference, TSet<[2, 3]>>;
```
A neat `select` method that picks all elements of a certain type:
```ts
type SetMixed = TSet<[1, 'hello', 3, true]>
type SetNumbers = TSet.select;
// ^? TSet<[1, 3]>
```
And `asUnionType`:
```ts
type SetMixed = TSet<[1, 'hello', 3, number]>
type Allowed = TSet.asUnionType;
// ^? 'hello' | number
```
NB: 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 -> number`, while `TSet<[1, number]>` will always preserve types as is.
### TMap
TMap maps a "key-type" to a "value-type". It has four usual map operations:
```ts
// set
type map1 = TMap.set;
// get
type Num = TMap.get;
// has
type Yep = TMap.has;
type Nope = TMap.has;
// remove
type EmptyAgain = TMap.remove;
```
Similar to `TSet`, you get a `select` method which can match key or value types:
```ts
type MixedMap = TMap<[[1, number], [2, number], ['hello', string]]>;
// select by key
type SetNumbers = TSet.select;
// ^? TMap<[[1, number], [2, number]]>
// select by value
type SetNumbers = TSet.select;
// ^? TMap<[['hello', string]]>
// select by both
type SetNumbers = TSet.select;
// ^? TMap<[]>
```
And `keys` / `values` that extract one map component as a tuple:
```ts
type MixedMap = TMap<[[1, number], [2, number], ['hello', string]]>;
type MixedKeys = TMap.keys;
// ^? [1, 2, 'hello']
type MixedValues = TMap.values;
// ^? [number, number, string]
// converts to union type:
type PossibleValues = TMap.values[number];
// ^? number | string
```
---
Built in 2023 by [Vladimir Klepov](https://blog.thoughtspile.tech)
Special thanks to:
- [expect-type](https://github.com/mmkal/expect-type) for awesome type-only test utils.
- [nano-staged](https://github.com/usmanyunusov/nano-staged) for quick prettier integration.
[MIT License](./LICENSE)