Coder Social home page Coder Social logo

haejunejung / ts-typekit Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 0.0 1.95 MB

🧰 A Collection of TypeScript Utility Types

Home Page: https://ts-typekit.vercel.app

License: MIT License

TypeScript 98.44% Shell 1.56%
ts typescript types tsd utility utility-types

ts-typekit's Introduction

ts-typekit

ts-typekit

🧰 Collection of TypeScript utility types

Version Downloads Type Coverage Software License

  • No Runtime Cost: ts-typekit operates exclusively at compile-time, meaning it introduces zero overhead to your runtime performance.

  • Battle-tested: All utility types within ts-typekit are rigorously tested using tsd, providing you with confidence and reliability in their usage.

  • Type Safety: By leveraging ts-typekit, you can enforce stricter type checks, reducing the likelihood of type-related errors and improving the overall robustness of your TypeScript applications.

Whether you're dealing with complex type transformations, conditional types, or need to enforce specific type constraints, ts-typekit has you covered with a growing collection of utility types that cater to a variety of common and advanced use cases.

Install

npm install ts-typekit
yarn add ts-typekit

Requires TypeScript >= 5.1

Usage

import type { StrictOmit } from 'ts-typekit';

type User = {
  id: number;
  name: string;
};

type UserWithoutId = StrictOmit<User, 'id'>; // { name: string }

Tip

Recommend working with the {strict: true} option in tsconfig.

If your team is using ESLint and wants to enforce the use of StrictOmit instead of the standard Omit, you can configure ESLint to help catch this. The @typescript-eslint/ban-types rule can be configured to display an error message when Omit is used, guiding developers to use StirctOmit instead. Here's how you can set up your ESLint configuration:

module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    // Include other relevant rules here
    '@typescript-eslint/ban-types': [
      'error',
      {
        types: {
          Omit: 'Use StrictOmit instead', // write the error message.
        },
        extendsDefaults: true,
      },
    ],
  },
};

Contributing

Read below for detailed contribution guide.

CONTRIBUTING

Contributors

Thanks for considering contributing to ts-typekit.

haejunejung
haejunejung

ts-typekit's People

Contributors

haejunejung avatar github-actions[bot] avatar dependabot[bot] avatar

Stargazers

 avatar  avatar

Watchers

 avatar

ts-typekit's Issues

Support for PickNonNullable

It provides support for PickNonNullable. This is a version of NonNullable within the Pick series (e.g., PickRequired, PickOptional). It is useful when we need to change specific properties to NonNullable while leaving others unchanged.

One practical example is managing form states, such as for email, password, or chips. These states typically remain NonNullable. However, if the original type does not include NonNullable, instead of creating a new type, using PickNonNullable is a good option.

Support for `ExtractRouteQueries`

It supports for ExtractRouteQueries.

Query strings are one of the most popular methods for passing data, providing input data along with a URL. It mainly consists of data in key and value format. I thought about automatically creating this into a type such as ExtractRouteParams in #14.

However, since query strings can handle a large number of cases, here it will proceed with a method of processing a total of 8 patterns.

  1. Handle simple key-value pairs.
  2. Processes arrays represented by repeated keys.
  3. Processes arrays expressed as comma-separated values.
  4. Handle object and array expressions using bracket notation.
  5. Handle JSON strings.
  6. Boolean value
  7. Numeric value
  8. Date and Time Format (ISO 8601)

It can parse looks like below.

type Query = "user[name]=John&user[age]=30&tags[]=red,tags[]=blue&startDate=2024-08-19T15:00:00Z&enabled=true&complexData={\"key\":\"value\"}";

type QueryParams = ExtractRouteQueries<Query>;

// Result will be:
type QueryParams = {
    user: {
        name: string;
        age: number;
    };
    tags: string[];
    startDate: Date;
    enabled: boolean;
    complexData: { key: string }; 
}

Support for `ReplaceAll`

It supports for ReplaceAll.

The replace method is commonly used to replace occurences of a specified substring within a string with another substring.

The Replace supports that in type system. #10

type Replaced = Replace<"ts-typekit", "-", "@">
// Result: "ts@typekit"

type Replaced = Replace<"ts-typekit-ts-typekit", "-", "@">
// Result: "ts@typekit-ts-typekit"

However, the Replace type only replaces the first occurrence of the specified substring, unlike ReplaceAll, which replaces all occurrences. To address this, it supports an { all: boolean } option, allowing to choose whether to replace just the first occurrence or all occurrences.

It can be used some cases such as Date format.

For example,

type Original = "01-01-2000"
type Changed = Replace(Original, "-", "/", { all: true }) // "01/01/2000"

Support for `WithPrefix`

It supports for `WithPrefix'.

The purpose of this type is to add a prefix to a string. For example as below.

type FormEventNames = "submit" | "reset" | "change";
type CaptializedFormEventNames = Capitalize<FormEventNames>; // 'Submit' | 'Reset' | 'Change'
type FormHandlerNames = WithPrefix<CaptializedFormEventNames, "on"> // "onSubmit" | "onReset" | "onChange";

It can be especially useful when deciding on a name.

  1. event handler.
  2. api endpoint.
  3. css class.
  4. state management action handlers.
  5. icon system.

Support for `PickRequired`

It provides support for PickRequired.

In TypeScript, there are situations where you need to make specifi properties of a type required, while keeping the rest of the type unchanged. The PickRequired type is designed for this purpose.

It allows you to select certain properties from a type and enforce that they are required, while leaving other properties as they are.

Here is a more detailed example.

type User = {
  id: number;
  name?: string;
  email?: string;
}

type RequiredUserProps = PickRequired<User, 'name' | 'email'>;
// Result will be: { id: number; name: string; email: string; }

Support for `Nullish`

It supports for Nullsh.

The Nullish type indicates the absence of a value or signifies that a variable has not been initalized. it's purpose is to handle optional properties, variables or function parameters that may no always have a value. It helps enhance the reliability of the code by explicitly handling these null or undefined cases. For example as below.

interface UserInfo {
  name: string;
  favoriteFood?: string | Nullish;
  dietRestrictions?: string | Nullish;
}

function getFoodRecommendations(user: UserInfo) {
  const favoriteFood = user.favoriteFood ?? 'Generic';
  const dietRestriction = user.dietRestrictions ?? 'No Special Diet';

  // In a real app, there could be a complex logic to get proper food recommendations.
  // Here, just return a simple string for demonstration.
  return `Recommendations: ${favoriteFood}, ${dietRestriction}`;
}

Support for `MapValues`

It provides support for MapValues. It is related to #44.

The MapValues type is useful when you need to work with just the values of a Map, allowing you to extract and use the value type.

type T0 = Map<string, string>;
type Expected = MapValues<T0> // string

type T1 = Map<string, '0' | '1' | '2'>;
type Expected = MapValues<T1> // '0' | '1' | '2'

Here's a detailed implementation. playground

Support for `NonEmptyArray`

It provides support for NonEmptyArray.

There are some situations where you need to ensure that an array is not empty before performing operations on it. Using NonEmptyArray helps you handle cases safely.

Here is a detailed example.

const numbersArray: number[] = [];
const getFirstValue = (array: number[]) => array[0];
console.log(getFirstValue(numbersArray) // undefined, there is no type error.

const numbersArray: NonEmptyArray<number> = [] // type error. It should have one or more elements.

const nonEmptyNumbersArray: NonEmptyArray<number> = [1,2,3];
const getFirstValue = (array: NonEmptyArray<number>) => array[0]; // we can handle only the success case.

Here's a detailed. playground.

Support for `Relation`.

It provides support for Relation.

It wrappers type for relation type definitions in entities. It is used to circumvent ESM modules circular dependency issue caused by reflection metadata saving the type of the property.

@link
https://github.com/typeorm/typeorm/blob/master/src/common/RelationType.ts

import { Entity, PrimaryGeneratedColumn, OneToOne } from "typeorm";

@Entity
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @OneToOne(() => User, user => user.profile)
  user: Relation<User>;
}

@Entity
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @OneToOne(() => Profile, profile => profile.user)
  profile: Relation<Profile>
}

Improve TypeScript Utility Types

Hello! I came across an interesting open-source project and wanted to share my thoughts.

In existing utility types like Omit or Pick, the Key part doesn't support auto-completion, which can be inconvenient. How about improving these utility types to enhance usability?

Below is an example of Omit. By using OmitKeyof, you can make it more convenient with auto-completion.

image

Support for `ArrayValues` & `ArrayIndices`

It provides support for ArrayValues & ArrayIndices.

ArrayValues and ArrayIndices help in working with constant arrays and tuples, ensuring type safety for accessing elements and indices.

ArrayValues extracts the values of a constant array or tuple as a union type.

const colors = ['red', 'green', 'blue'] as const;

type Color = ArrayValues<typeof colors> // 'red' | 'green' | 'blue'

ArrayIndices extracts valid indices for a constant array or tuple as a untion type of numbers;

const colors = ['red', 'green', 'blue'] as const;

type ColorIndex = ArrayIndices<typeof colors> // 0 | 1 | 2

Here's a complete example using both.

const colors = ['red', 'green', 'blue'] as const;

type Color = ArrayValues<typeof colors> // 'red' | 'green' | 'blue'

type ColorIndex = ArrayIndices<typeof colors> // 0 | 1 | 2

const getColorName = (index: ColorIndex): Color => colors[index];

Here's playground.

Support for `SnakeCase`

It support for SnakeCase that converts string to snake case. For example as below.

type T1 = SnakeCase<"ts-typekit"> // ts_typekit
type T2 = SnakeCase<"tsTypekit"> // ts_typekit

To satisfy this, it is necessary to first break the string into words. This will be implemented with GetWords in '_internal'.

It will be made into an arrray of words. For example as below.

type Words1 = GetWords<"ts-typekit"> // ["ts", "typekit"]
type Words2 = GetWords<"snake_case"> // ["snake", "case"]

Support for `IsTuple`

It provides support for IsTuple.

Tuples and arrays are handled differently in TypeScript. Tuples have a fixed length with potentially different types at each position, while arrays are variable-length and typically homogeneous. It allows you to apply specific login or constraints that are unique to tuples versus array.

For example as below.

type T0 = IsTuple<[number]> // true
type T1 = IsTuple<[number, string> // true

type T2 = IsTuple<[]> // false
type T3 = IsTuple<number[]> // false
type T4 = IsTuple<Array<number>> // false

Support for `UnionToTuple`

It provides support for UnionToTuple.

I think it is useful when we need to change the union to tuple. For example.,

type Category = 'admin' | 'editor' | 'viewer';

type CategoryTuple = UnionToTuple<Category>
// ['admin', 'editor', 'viewer']

Support for `Brand`

It supports for Brand.

TypeScript basically uses a structural type system. In this system, types are considered based on the structure of the object (properties and their types). That is, if two objects have the same properties, they are considered the same type. For example as below.

declare function getPost(postId: number): Promise<Post>;
declare function getUser(userId: number): Promise<User>;

type User = {
  id: number;
  name: string;
}

type Post = {
  id: number;
  authorId: number;
  title: string;
  description: string;
  content: string;
}

// If we should have passed `post.authorId`, it should spot the bug. But it is not.
async function authorOfPost (postId: number): Promise<User> {
  return await getPost(postId).then(post => getUser(post.id));
}

// It is the correct way.
async function authorOfPost (postId: number): Promise<User> {
  return await getPost(postId).then(post => getUser(post.authorId))
}

Brand is a way to deal with nominal type systems. Even if they have the same structure, types with different names are considered different types, so they can be useful in a variety of situations.

declare function getPost(postId: Post['id']): Promise<Post>;
declare function getUser(userId: User['id']): Promise<User>;

interface User {
  id: Brand<number, 'user'>;
  name: string;
}

interface Post {
  id: Brand<number, 'post'>;
  authorId: User['id'];
  title: string;
  body: string;
}

I think it is nice case if you want to use a nominal type system 👍

Support for `IsAny`

It provides support for IsAny. It useful in type utilities such as disallowing any's to be passed to a function.

@link https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360

It can be implemented as below.

type IsAny<T> = 0 extends 1 & T ? true : false;

How it can work?
& is an intersection type. 1&T can be seen as defining the common type of 1 and T. Here, we take advantage of the fact that any always returns any when it interescts with another type. For other types, if you use an interesction type, it points to a specific type called number, never, or 1 as a common type. So the above IsAny can work normally.

Support for `IsNever`

It provides support for IsNever. microsoft/TypeScript#31751

It helps when checking if something does not occur.

type IsNever1<T> = T extends never ? true : false;

type T0 = IsNever1<never>; // never

type IsNever2<T> = [T] extends [never] ? true : false;

type T1 = IsNever2<never> // true

Support for `Simplify`

It provides support for Simplify.

The Simplify utility type is needed to simplify complex TypeScript types by merging and resolving their properties into a single, flattened structure. This helps avoid issues with type assignability and makes type hints in editors cleaner. It ensures compatability when passing types to functions that expect specific type shapes. microsoft/TypeScript#15300

type TA = {
  aa: number;
  ab: number;
};

type TB = {
  ba: number;
  bb: number;
};

type TC = TA & TB;
// Result: TA & TB;

type TD = Simplify<TA & TB>;
// Result: { aa: number; ab: number; ba: number; bb: number; }

Support for `IsFunction`

It provides support for IsFunction.

Functions are distinct from objects, arrays, and tuples. Identifying functions separately allows you to apply function-specific logic or type handling.

For example as below.

type T0 = IsFunction<Function> // true
type T1 = IsFunction<() => void> // true
type T2 = IsFunction<(a: number, b: number) => number> // true

type T4 = IsFunction<[number]> // false
type T5 = IsFunction<{}> // false
type T6 = IsFunction<Record<any, any>> // false
type T7 = IsFunction<[]> // false

Support for `ExtractRouteParams`

It supports for ExtractRouteParams

The purpose of this type is to extract the params from a route. For example as below.

type ApiPath = "/users/:userId/rewiews/reviewId";

type ExtractedApiPath = ExtractRouteParams<ApiPath> 
// Result will be:
// type ExtractedApiPath = {
//   userId: string;
//   reviewId: string;
// }

Use cases:

  • Create custom React hook which will return existing params for current URL
const { userId, reviewId } = useTypedParams<ApiPath>();
  • Replace URL Params to build link for navigation
function buildLink<Route extends string>(
  template: Route, 
  params: ExtractRouteParams<Route>
): string {
  return template.replace(/:([a-zA-Z]+)/g, (_, key) => {
    const typedKey = key as keyof typeof params;
    const value = params[typedKey];
    
    if (value !== undefined) {
      return value as string;
    }
    
    throw new Error(`Missing parameter: ${key}`);
  });
}

const url = buildLink("/users/:userId/products/:productId", { userId, productId });

Support for `Split`

The well known split method splits a string into an array of substrings by lookig for a separator, and returns the new array.

It supports for Split that is to split a string by using separator but in the type system.

For example,

type Separated = Split<"ts-typekit", "-"> 
// It should be ["ts", "typekit"]

It is useful for some cases. It can be used in ApiSpec.

For example,

type ExtractParams<ApiPath extends string> =
  Split<ApiPath, '/'> extends Array<infer Part>
    ? { [K in Part extends `${string}:${infer Param}` ? Param : never]: string }
    : {}

// Example API path
type ApiPath = '/products/:productId/reviews/:reviewId'

// Extract parameters from the example API path
type ApiParams = ExtractParams<ApiPath>

// Resulting type will be:
// type ApiParams = {
//   productId: string;
//   reviewId: string;
// }

Support for `IsArray`

It provides support for IsArray.

It allows you to accurately determine if a type is an array or not. This helps in distinguish between array and non-array types. By ensuring that a type is an array before performing operations, It helps prevent runtime erros and enhances code reliability.

For example as below.

type T0 = IsArray<[]> // true
type T1 = IsArray<readonly []> // true
type T2 = IsArray<number[]> // true

type T3 = IsArray<{}> // false
type T4 = IsArray<Record<number, any>> // false

Support for `PickOptional`

It provides support for PickOptional. Related to #46.

In TypeScript, there are situations where you need to make specific properties of a type optional, while keeping the rest of the type unchanged. The PickOptional type is designed for this purpose.

It allows you to select certain properties from a type and enforce that they are optional, while leaving other properties as they are.

Here is a more detailed example.

type User = {
  id: number;
  name: string;
  email: string;
}

type NameOptionalUserProps = PickOptional<User, 'name'>;
// Result will be: { id: number; name?: string; email: string; }

Support for `IsEqual`

It provides support for IsEqual.

The TypeScript type system is highly functional, and type-level testing is often required. However, checking type equivalence is not straightforward.

IsEqual helps determin whether two types are equivalent.

Support for `Replace`

The replace method is commonly used to replace occurences of a specified substring within a string with another substring.

The Replace supports that in type system. For example,

type Replaced = Replace<"ts-typekit", "-", "@">
// Result: "ts@typekit"

type Replaced = Replace<"ts-typekit-ts-typekit", "-", "@">
// Result: "ts@typekit-ts-typekit"

Support for `Prefixify`

It supports for Prefixify.

The purpose of Prefixify is to add a prefix to all keys of an object to prevent naming collisions an improve clarity. For example as below.

type Original = {
  name: string;
  age: number;
}

type Prefixed = Prefixify<Original, "user_">;
// Result:
// type Prefixed = {
//   user_name: string;
//   user_age: number;
// }

It is useful some cases.

  1. API reponse transformation.
  2. Namespace collision prevention.
  3. Form data management.
  4. Theming Systems.
  5. Localiaztion keys.

Support for PickReadonly

It provides support for PickReadonly. This is a version of Readonly within the Pick series (e.g., PickRequired, PickOptional). I find it quite useful when we need to change specific properties to readonly while leaving others.

Support for `MapKeys`

It provides support for MapKeys.

Map is a key-value data structure commonly used for its efficiency, such as in hash maps. The MapKeys type is useful when you need to work with just the keys of a Map, allowing you to extract and use the key type independently from the value type.

type T0 = Map<string, string>;
type Expected = MapKeys<T0> // string

type T1 = Map<'0' | '1' | '2', string>;
type Expected = MapKeys<T1> // '0' | '1' | '2'

Here is a detailed implementation. playground

Support for `TupleToUnion`

It provides support for TupleToUnion.

There are some situations that need to change tuple to union type.
It is more detailed example for API Response that is used TupleToUnion.

type ApiResponse = [number, string, boolean, { key: string }];
type AllowedValues = TupleToUnion<ApiResponse>; // number | string | boolean | { key: string }

function handler (response: AllowedValues) {
 if (typeof response === 'number') { // }
 else if (typeof response === 'string) { // }
 else if (typeof response === 'boolean) { // }
 else { // }
}

⛳: Setup the `ts-kit`

  • Setup mono repository
  • Setup packages (typescript v5+, tsd, prettier)
  • Setup github actions (add-assignee, add-contributors, npm-release)
  • Setup docs using vitepress
  • Setup Readme.md
  • Setup Contributing.md (ko, en)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.