npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@asosunoff/ts-tools

v1.1.15

Published

TS-TOOLS

Readme

TypeScript - Tools

TypeScript type-level utility collection.

Install

npm i @asosunoff/ts-tools

Usage

import type {
  any as anyTools,
  array,
  common,
  object,
  string,
} from "@asosunoff/ts-tools";

You can also import commonly used utilities directly from the package root.

import type {
  ArrayType,
  ArrayLast,
  ConstantMap,
  DtoToModel,
  DtoWithCamelCaseKeys,
  DtoWithCamelCaseKeysRecursive,
  FilterKeys,
  FlattenedModelRecord,
  GetOptionalKeys,
  GetTypeByPropsPath,
  HasOnlyKeys,
  HasSnakeCaseKey,
  isArray,
  IsPrimitiveType,
  IsRequiredKey,
  IsSameType,
  OverrideFields,
  PropsPath,
  RemoveFields,
  SnakeToCamelCase,
  StrictRecord,
  TakeSnakeCaseKey,
} from "@asosunoff/ts-tools";

Example:

import type { object, string } from "@asosunoff/ts-tools";

type ApiUser = {
  user_id: number;
  first_name: string;
  last_name?: string;
};

type ClientUser = object.DtoWithCamelCaseKeys<ApiUser>;
// {
//   userId: number;
//   firstName: string;
//   lastName?: string;
// }

type UserOptionalKeys = object.GetOptionalKeys<ApiUser>;
// "last_name"

type UserPatch = object.OverrideFields<
  ClientUser,
  { firstName: "Admin" }
>;
// {
//   userId: number;
//   firstName: "Admin";
//   lastName?: string;
// }

type PublicUser = object.DtoToModel<
  ApiUser,
  { userId: string },
  { deleteFields: "lastName" }
>;
// {
//   userId: string;
//   firstName: string;
// }

type NestedApiUser = {
  user_profile: {
    first_name: string;
    address_info: {
      zip_code: string;
    };
  };
};

type NestedClientUser = object.DtoWithCamelCaseKeysRecursive<NestedApiUser>;
// {
//   userProfile: {
//     firstName: string;
//     addressInfo: {
//       zipCode: string;
//     };
//   };
// }

type UserSearchRecord = object.FlattenedModelRecord<
  NestedApiUser,
  {
    userProfileFirstName: string;
    userProfileAddressInfoZipCode: string;
  }
>;
// {
//   userProfileFirstName: string;
//   userProfileAddressInfoZipCode: string;
// }

type FieldName = string.SnakeToCamelCase<"created_at">;
// "createdAt"

type HasSnakeCaseField = string.HasSnakeCaseKey<"created_at">;
// true

type ApiSnakeCaseKeys = object.TakeSnakeCaseKey<ApiUser>;
// "user_id" | "first_name" | "last_name"

type Priority = "high" | "middle" | "low";

const RecommendationPriority: object.ConstantMap<Priority> = {
  High: "high",
  Middle: "middle",
  Low: "low",
};

Array Utilities

array.First<T>

Returns the first tuple item.

type Result = array.First<["a", "b", "c"]>;
// "a"

array.Last<T>

Returns the last tuple item.

type Result = array.Last<["a", "b", "c"]>;
// "c"

array.Pop<T>

Removes the last tuple item.

type Result = array.Pop<["a", "b", "c"]>;
// ["a", "b"]

array.Push<T, E>

Adds an item to the end of a tuple.

type Result = array.Push<["a", "b"], "c">;
// ["a", "b", "c"]

array.Shift<T>

Removes the first tuple item.

type Result = array.Shift<["a", "b", "c"]>;
// ["b", "c"]

array.Unshift<T, E>

Adds an item to the start of a tuple.

type Result = array.Unshift<["b", "c"], "a">;
// ["a", "b", "c"]

array.Join<T, D>

Joins tuple items into a string literal.

type Result = array.Join<["user", "profile", "name"], ".">;
// "user.profile.name"

array.Length<T>

Returns tuple length.

type Result = array.Length<["a", "b", "c"]>;
// 3

array.ArrayType<T>

Returns the item type from an array type.

type Result = array.ArrayType<Array<{ id: number; name: string }>>;
// {
//   id: number;
//   name: string;
// }

array.isArray<T>

Checks whether a type is an array.

type A = array.isArray<string[]>;
// true

type B = array.isArray<string>;
// false

String Utilities

string.Split<S, D>

Splits a string literal by delimiter.

type Result = string.Split<"user.profile.name", ".">;
// ["user", "profile", "name"]

string.Length<T>

Returns string literal length.

type Result = string.Length<"hello">;
// 5

string.Last<T>

Returns the last character of a string literal.

type Result = string.Last<"hello">;
// "o"

string.First<T, D>

Returns the first string part. The delimiter defaults to an empty string.

type FirstChar = string.First<"hello">;
// "h"

type FirstPart = string.First<"user.profile.name", ".">;
// "user"

string.Tail<T, D>

Returns the string after the first delimiter match.

type Result = string.Tail<"user.profile.name", ".">;
// "profile.name"

string.Replace<T, A, B>

Replaces the first occurrence of a string literal.

type Result = string.Replace<"user-name", "-", "_">;
// "user_name"

string.ReplaceAll<T, A, B>

Replaces all occurrences of a string literal.

type Result = string.ReplaceAll<"user-profile-name", "-", "_">;
// "user_profile_name"

string.At<S, I>

Returns a character by index.

type Result = string.At<"hello", 1>;
// "e"

string.SnakeToCamelCase<T>

Converts a snake_case string literal to camelCase.

type Result = string.SnakeToCamelCase<"user_profile_name">;
// "userProfileName"

string.HasSnakeCaseKey<K>

Checks whether a property key is a string key containing _.

type A = string.HasSnakeCaseKey<"user_id">;
// true

type B = string.HasSnakeCaseKey<"userId">;
// false

string.FilterKeys<T, R>

Removes keys from a union.

type Result = string.FilterKeys<"id" | "name" | "email", "email">;
// "id" | "name"

Object Utilities

Key-check helpers use different directions:

| Utility | Checks | | --- | --- | | object.HasKeys<K, T> | every key in K exists in T | | object.HasOnlyKeys<K, T> | every key in T is allowed by K | | object.HasExactKeys<K, T> | both previous checks are true | | object.StrictRecord<K, T> | requires T to be a strict Record<K, unknown> |

object.Merge<A, B>

Merges two object types. Properties from B override properties from A.

type Result = object.Merge<
  { id: number; name: string; enabled: boolean },
  { name: "admin"; role: "owner" }
>;
// {
//   id: number;
//   name: "admin";
//   enabled: boolean;
//   role: "owner";
// }

object.isObject<T>

Checks whether a type is an object record. Arrays return false.

type A = object.isObject<{ id: number }>;
// true

type B = object.isObject<string[]>;
// false

object.PropsPath<T>

Builds a union of nested leaf property paths. Pass true as the second parameter to include intermediate object paths too.

type Result = object.PropsPath<{
  user: {
    profile: {
      name: string;
    };
  };
  active: boolean;
}>;
// "user.profile.name" | "active"

type ArrayPaths = object.PropsPath<{
  users: Array<{ id: number }>;
  tags: string[];
}>;
// "users.id" | "tags"

type TuplePaths = object.PropsPath<{
  tags: [{ name: string }, { id: number }];
}>;
// "tags.name" | "tags.id"

type AllPaths = object.PropsPath<
  {
    user: {
      profile: {
        name: string;
      };
    };
    active: boolean;
  },
  true
>;
// "user" | "user.profile" | "user.profile.name" | "active"

object.FlattenedModelRecord<T, R, F>

Validates that R contains flattened keys generated from nested property paths in T.

Each path is joined with _ and then converted from snake_case to camelCase.

type Result = object.FlattenedModelRecord<
  {
    user: {
      first_name: string;
      address_info: {
        zip_code: string;
      };
    };
    active: boolean;
  },
  {
    userFirstName: string;
    userAddressInfoZipCode: string;
  },
  "active"
>;
// {
//   userFirstName: string;
//   userAddressInfoZipCode: string;
// }

Without F, every flattened key is required in R.

object.GetTypeByPropsPath<T, PATH>

Returns the value type by a dot-separated property path.

type Result = object.GetTypeByPropsPath<
  {
    user: {
      profile: {
        name: string;
      };
    };
  },
  "user.profile.name"
>;
// string

type Profile = object.GetTypeByPropsPath<
  {
    user: {
      profile: {
        name: string;
      };
    };
  },
  "user.profile"
>;
// { name: string }

type UserId = object.GetTypeByPropsPath<
  {
    users: Array<{ id: number }>;
  },
  "users.id"
>;
// number

object.HasKeys<K, T>

Checks whether every key in K exists in T.

type A = object.HasKeys<
  "id",
  {
    id: number;
    name: string;
  }
>;
// true

type B = object.HasKeys<
  "id" | "email",
  {
    id: number;
    name: string;
  }
>;
// false

object.HasOnlyKeys<K, T>

Checks whether T has no keys outside K.

type KeysList = "alert" | "confirm";

type A = object.HasOnlyKeys<
  KeysList,
  {
    alert: { title: string };
    confirm: { message: string };
  }
>;
// true

type B = object.HasOnlyKeys<
  KeysList,
  {
    alert: { title: string };
    confirm: { message: string };
    custom: { id: string };
  }
>;
// false

object.HasExactKeys<K, T>

Checks whether T has exactly the selected key union.

type Result = object.HasExactKeys<
  "id" | "name",
  {
    id: number;
    name: string;
  }
>;
// true

object.StrictRecord<K, T>

Requires T to be a record with every key from K and no keys outside K.

type KeysList = "alert" | "confirm";

type A = object.StrictRecord<
  KeysList,
  {
    alert: { title: string };
    confirm: { message: string };
  }
>;
// {
//   alert: { title: string };
//   confirm: { message: string };
// }

type B = object.StrictRecord<
  KeysList,
  {
    alert: { title: string };
    confirm: { message: string };
    custom: { id: string };
  }
>;
// Error: "custom" is not allowed.

object.ConstantMap<T>

Creates a record type where keys are capitalized string union values and values are the original strings from that union.

type Priority = "high" | "middle" | "low";

type Result = object.ConstantMap<Priority>;
// {
//   High: "high";
//   Middle: "middle";
//   Low: "low";
// }

Snake-case values are converted to camel case before capitalization.

type Status = "in_progress" | "completed";

const RecommendationStatus: object.ConstantMap<Status> = {
  InProgress: "in_progress",
  Completed: "completed",
};

object.CollapseObject<T>

Converts a nested object type into a flat object with dot-separated keys.

type Result = object.CollapseObject<{
  user: {
    profile: {
      name: string;
    };
  };
  active: boolean;
}>;
// {
//   "user.profile.name": string;
//   active: boolean;
// }

object.PartialField<T, F>

Makes selected object fields optional and keeps all other fields unchanged.

type Result = object.PartialField<
  {
    id: number;
    name: string;
    email: string;
  },
  "email"
>;
// {
//   id: number;
//   name: string;
//   email?: string;
// }

object.RequiredField<T, K>

Makes selected object fields required and keeps all other fields unchanged.

type Result = object.RequiredField<
  {
    id: number;
    name?: string;
    email?: string;
  },
  "name" | "email"
>;
// {
//   id: number;
//   name: string;
//   email: string;
// }

object.IsOptionalKey<T, K>

Checks whether object key K is optional.

type Result = object.IsOptionalKey<
  {
    id: number;
    name?: string;
  },
  "name"
>;
// true

object.IsRequiredKey<T, K>

Checks whether object key K is required.

type Result = object.IsRequiredKey<
  {
    id: number;
    name?: string;
  },
  "id"
>;
// true

object.IsRequeredKey<T, K> is kept as a backward-compatible alias, but object.IsRequiredKey<T, K> should be preferred.

object.GetOptionalKeys<T>

Returns a union of all optional keys in T.

type Result = object.GetOptionalKeys<{
  id: number;
  name?: string;
  email?: string;
}>;
// "name" | "email"

object.GetRequiredKeys<T>

Returns a union of all required keys in T.

type Result = object.GetRequiredKeys<{
  id: number;
  name?: string;
  active: boolean;
}>;
// "id" | "active"

object.OverrideFields<T, U>

Overrides existing fields in T using U.

Unlike Merge, this utility only allows keys that already exist in T.

type Result = object.OverrideFields<
  {
    id: number;
    role: "user";
    active: boolean;
  },
  {
    role: "admin";
    active: false;
  }
>;
// {
//   id: number;
//   role: "admin";
//   active: false;
// }

object.RemoveFields<T, K>

Removes selected fields from an object type.

type Result = object.RemoveFields<
  {
    id: number;
    name: string;
    password: string;
  },
  "password"
>;
// {
//   id: number;
//   name: string;
// }

object.DtoWithCamelCaseKeys<T>

Converts top-level object keys from snake_case to camelCase.

type Result = object.DtoWithCamelCaseKeys<{
  first_name: string;
  last_name: string;
  is_active: boolean;
}>;
// {
//   firstName: string;
//   lastName: string;
//   isActive: boolean;
// }

object.TakeSnakeCaseKey<T>

Extracts a union of object keys written in snake_case.

type Result = object.TakeSnakeCaseKey<{
  user_id: number;
  firstName: string;
  created_at: string;
}>;
// "user_id" | "created_at"

object.DtoToModel<T, R, Options>

Converts a DTO into a model by converting keys to camelCase, overriding selected field types, and removing selected model fields.

R is intended for real type changes after DTO keys are converted to camelCase. If a primitive field is overridden with the same type, TypeScript reports a redundant override. Options["deleteFields"] removes fields from the final model.

type Result = object.DtoToModel<
  {
    user_id: number;
    created_at: string;
    password_hash: string;
  },
  {
    createdAt: Date;
  },
  {
    deleteFields: "passwordHash";
  }
>;
// {
//   userId: number;
//   createdAt: Date;
// }

Override keys must be written in camelCase. If a legacy model still needs a snake_case override, Options["skipSnakeCaseCheck"] disables that diagnostic for selected keys. Use Options["deleteFields"] if the converted camelCase field should be removed from the final model.

type Result = object.DtoToModel<
  {
    user_id: number;
  },
  {
    user_id: string;
  },
  {
    deleteFields: "userId";
    skipSnakeCaseCheck: "user_id";
  }
>;
// {
//   user_id: string;
// }

Options["checkObjectFields"] enables same-type diagnostics for object fields, and Options["skipTypeCheckKeys"] disables same-type diagnostics for selected keys.

type Result = object.DtoToModel<
  {
    status_id: number;
  },
  {
    statusId: number;
  },
  {
    skipTypeCheckKeys: "statusId";
  }
>;
// {
//   statusId: number;
// }

object.DtoWithCamelCaseKeysRecursive<T>

Recursively converts nested object keys from snake_case to camelCase.

Arrays are preserved as-is and are not traversed recursively.

type Result = object.DtoWithCamelCaseKeysRecursive<{
  user_profile: {
    first_name: string;
    address_info: {
      zip_code: string;
    };
  };
  is_active: boolean;
}>;
// {
//   userProfile: {
//     firstName: string;
//     addressInfo: {
//       zipCode: string;
//     };
//   };
//   isActive: boolean;
// }

Any Utilities

anyTools.Anyfy<O>

Keeps object keys and converts all values to any.

type Result = anyTools.Anyfy<{
  id: number;
  name: string;
}>;
// {
//   id: any;
//   name: any;
// }

Common Utilities

common.Prettify<T>

Flattens an intersection or mapped type for cleaner editor hints.

type Result = common.Prettify<
  { id: number } & {
    name: string;
  }
>;
// {
//   id: number;
//   name: string;
// }

common.EmitListeners<T>

Builds listener props from an event map.

type Result = common.EmitListeners<{
  change: [value: string];
  submit: [id: number, valid: boolean];
}>;
// {
//   onChange?: (value: string) => void;
//   onSubmit?: (id: number, valid: boolean) => void;
// }

common.SplitCamelCase<T, Separator>

Inserts a separator before uppercase characters.

type Result = common.SplitCamelCase<"userProfileName">;
// "user-profile-name"

common.IsFunction<T>

Checks whether a type is a function.

type A = common.IsFunction<() => void>;
// true

type B = common.IsFunction<string>;
// false

common.MaybeRefProps<T>

Wraps non-function properties into a value-or-ref-like shape. Function properties stay unchanged.

type Result = common.MaybeRefProps<{
  value: string;
  count: number;
  onChange: (value: string) => void;
}>;
// {
//   value: string | { value: string };
//   count: number | { value: number };
//   onChange: (value: string) => void;
// }

common.TypeCheck<T>

Converts a type into a generic function predicate for strict type comparison.

type Result = common.TypeCheck<string>;
// <G>() => G extends string ? 1 : 2

common.IsSameType<T, U>

Checks whether two types are exactly equivalent.

type A = common.IsSameType<string, string>;
// true

type B = common.IsSameType<string, string | number>;
// false

common.PrimitiveType

Union of primitive values used by type-level diagnostics.

type Result = common.PrimitiveType;
// string | number | boolean | bigint | symbol | null | undefined

common.IsPrimitiveType<T>

Checks whether a type is a primitive or a union of primitives.

type A = common.IsPrimitiveType<string | number>;
// true

type B = common.IsPrimitiveType<{ id: number }>;
// false