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]].