ts-gems
v4.0.4
Published
Valuable typing extensions for TypeScript
Maintainers
Readme
ts-gems
Think of it as lodash — but every function operates on types, not values, and runs at compile time for free.
Quick question: what's the type of Partial<{ user: { name: string } }>['user']?
If you guessed { name?: string }, that's the intuitive answer — and it's wrong.
TypeScript's built-in Partial<T> only reaches the first level. The real
answer is { name: string } — still fully required underneath, still ready
to blow up at runtime the moment you try to build one incrementally.
type Config = { user: { name: string; age: number } };
type Shallow = Partial<Config>;
// { user?: { name: string; age: number } } <- still required inside!
type Deep = DeepPartial<Config>;
// { user?: { name?: string; age?: number } } <- actually usableThat one gap is the whole reason this library exists. Partial, Required,
Readonly, and Pick/Omit all share the same blind spot: they stop at the
surface. ts-gems finishes what TypeScript started — and then keeps going into
places you didn't know needed a type yet.
A few things that might surprise you
Freeze a config tree, arrays included, in one line:
import type { DeeperReadonly } from 'ts-gems';
type FrozenConfig = DeeperReadonly<{
servers: { host: string; port: number }[];
}>;
// every level is readonly — including inside the array elementsTurn any interface into an API-safe DTO — no functions, no symbols, recursively:
import type { DTO } from 'ts-gems';
class User {
name = '';
greet() {}
[Symbol.iterator]() {}
}
type UserResponse = DTO<User>;
// { name: string } — methods and symbol keys are gone, nested objects tooStop mixing up two numbers that were never meant to meet:
import type { Opaque } from 'ts-gems';
type UserId = Opaque<number, 'UserId'>;
type OrderId = Opaque<number, 'OrderId'>;
function cancelOrder(id: OrderId) {
/* ... */
}
declare const userId: UserId;
cancelOrder(userId); // ✗ compile error — nominally different, even though both are `number`Merge types without the intersection trap:
type A = { name: string };
type B = { name: Date }; // overlapping key, different type
type Bad = A & B; // name: string & Date — a type nothing can satisfy. Oops.
type Good = Combine<A, B>; // { name: string } — A simply winsNone of this is magic. It's ~60 small, focused utility types, each solving one specific gap — composable, dependency-free, and fully documented with runnable examples.
Explore the full toolkit
📖 Browse the complete API reference →
| Category | What it does |
| --- | --- |
| Deep* / Deeper* family | Mutable, Readonly, Partial, Required, Nullish — that finally reach nested objects and arrays |
| DTO / PartialDTO / PatchDTO | Turn any class or interface into a clean transfer-object shape |
| Pick / Omit family | Select by key, by function-vs-data, or by matching value type |
| Opaque | Nominal typing / branded primitives for TypeScript's structural type system |
| Combine | Merge types without the & intersection trap |
| 20+ type guards | IfAny, IfNever, IfEquals, IfTuple, IfCompatible, and more, for building your own conditional types |
| And / Or | Compile-time boolean logic to combine several guards into one |
Installation
npm install ts-gems --saveimport { DeepPartial, DTO, Opaque, StrictOmit } from 'ts-gems';Everything is exported from the package root — no sub-path imports, no
runtime cost. It's types all the way down (with twelve tiny as* cast
helpers thrown in, purely for ergonomics).
Node Compatibility
- node >= 16.x
License
ts-gems is available under the MIT license.
