Skip to content

Repository files navigation

ts-gems logo

ts-gems

NPM Version NPM Downloads CI Tests

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 usable

That 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 elements

Turn 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 too

Stop 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 wins

None 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 --save
import { 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.

About

Powerful typing extensions for TypeScript

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages