Very easy to run into crazy type definitions. I'm seeing them a lot in [[Prisma]]. For example:
```typescript
type XOR<T, U> =
T extends object ?
U extends object ?
(Without<T, U> & U) | (Without<U, T> & T)
: U : T
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
```
A lot of code can be found [here](https://github.com/prisma/prisma/blob/7f20fe059925d006e07112995fc6b928c4a14d27/packages/client/src/generation/TSClient/common.ts#L362)
https://github.com/sindresorhus/type-fest/tree/main
Typescript ships with some utility types ([link](https://www.typescriptlang.org/docs/handbook/utility-types.html)), but no new additional ones would be added; see:
https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
# Data Validation Libraries
- [[TypeBox]] is a good example, where you get very organic relationship between the validators and TS types. Few other similar libraries;
- zod
- react-form-hook, which can hook into typebox / zod.
- https://news.ycombinator.com/item?id=41764163 Effect
- [[Github]]'s octokit;
> [!warning]
> One side effect of designing the API like this is, it's very difficult to know what are the actual return types. https://octokit.github.io/types.ts/interfaces/Endpoints.html defines all endpoints, which is imported from https://github.com/octokit/openapi-types.ts/blob/main/packages/openapi-types/types.d.ts
# Frameworks and Libraries that are purely (or mostly) types
#todo
Typescript allows you to use string literals as if they are symbols;
- [[#Data Validation Libraries]]
- ORMs
- query builders
# Gotchas
## The Perils of `{}`
> [!warning]
> Error: Don't use `{}` as a type. `{}` actually means "any non-nullish value".
> - If you want a type meaning "any object", you probably want `object` instead.
> - If you want a type meaning "any value", you probably want `unknown` instead.
> - If you want a type meaning "empty object", you probably want `Record<string, never>` instead.
> - If you really want a type meaning "any non-nullish value", you probably want `NonNullable<unknown>` instead. `@typescript-eslint/ban-types`
# Avoiding [[Naming Things]]
> [!note] related: [[Rust]]'s *unnamable* types.
This isn't even a property of an advanced typesystem, but an *ergonomic* typesystem. A typesystem that allows you to refer to things via references instead of explicit names is convenient, especially as some auto-inferred types become unwieldy to articulate.
## Type Traversal
```typescript
// type traversal.
type User = {
username: string;
address: {
state: string;
country: string;
// ...
};
};
type Address = User["address"];
```
this is convenient as one can define the entire `User` type in one shot, which reads well without any jumps.
Convenient Utilities:
- Type traversals. Note that one can use `[number]` or `[string]` to traverse into arrays / objects.
- `ReturnType<T>`
- `Parameters<T>`, especially combined with the above type traversals, this is really powerful.
- `Awaited<T>` - allows to resolve promises.
Example of using `(typeof arrayVariable)[number]` in [[Typescript - String Unions|String Unions]].
## `ReturnType<T>`
This is convenient when one's refactoring a gnarly logic that involves inferred types.
```typescript
function foo() {
return {
foo: 42,
bar: "hello",
...
}
}
const x: ReturnType<typeof foo> = foo();
```
# Utility Types and Keywords
## `Record<Key, Value>`
This has different semantic between union types and "open" types like `string`.
if Key is an union type - say `type Key = "foo" | "bar" | "baz"`, then `Record`'s keys must be exhausted. thus `record[key]` cannot yield `undefined`. however, for `string`s, it doesn't need to be that (it automatically has `Partial<...>` behavior).
## NoInfer
- https://www.totaltypescript.com/noinfer
## Infer
Used to capture type variables via conditionals; this is useful for capturing type variables.
```typescript
type Element<T> = T extends Array<infer Item> ? Item : never;
Element<string[]> // string
```
## Mapped Types
https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
Mapped Types, along with Tuple Types, provide rudimentary support for higher-order types.
The most basic syntax is:
```typescript
[P in keyof Source] : Source[P]
```
# Patterns
## Testing Types
- [[Vitest]]'s https://vitest.dev/guide/testing-types is handy;
- However, negation (i.e. "this should fail to compile" can only be tested via [@ts-expect-error](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#-ts-expect-error-comments))
## Type-aware select
Inspired by [[Prisma]].
```typescript
type Filter<T> = {
[P in keyof T]?: boolean;
};
type Selected<T, F> = {
[P in keyof F]?: P extends keyof T ? T[P] : never;
};
/**
* Given an input and boolean mapping, only return fields with true mapping.
*/
function select<T, F extends Filter<T>>(input: T, filter: F): Selected<T, F> {
const result: Selected<T, F> = {};
for (const k of Object.keys(filter)) {
// gotta force some casting; ts (or me) isn't smart enough for this.
if (!filter[k as keyof F]) continue;
const key = k as keyof T;
const value = input[key] as Selected<T, F>[keyof T];
result[key] = value;
}
return result;
}
const x = select({ a: 1, b: ["a", "b"], c: "foo" }, { a: true, b: false });
```
The `select` function is typesafe - in the above example, `x` has the signature of `{ a : number, b : string[] }` and `x.c` would not compile.
## [[Typescript - Branded Types]]
## OneOf
```typescript
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U;
type OneOf<T extends any[]> = T extends [infer Only]
? Only
: T extends [infer A, infer B, ...infer Rest]
? OneOf<[XOR<A, B>, ...Rest]>
: never;
```
## Prettify / Simplify
```typescript
export type Simplify<T> = {
[KeyType in keyof T]: T[KeyType];
} & {};
```
Source: https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
# Antipatterns
- #antipattern Hyper-typing - https://pscanf.com/s/341/
- Similar post - shitty types - https://lucumr.pocoo.org/2025/8/4/shitty-types/ , in the context of [[Vibe Coding]].