Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: A bunch of new utilities #138

Open
eyedean opened this issue Mar 28, 2020 · 7 comments
Open

Suggestion: A bunch of new utilities #138

eyedean opened this issue Mar 28, 2020 · 7 comments
Labels
enhancement New feature or request v10.1

Comments

@eyedean
Copy link

eyedean commented Mar 28, 2020

Hi,

I am so excited that I found this repo! I have been doing similar things in a local file in my project and now I can replace it with an open-source, community managed dependency!

I have the following list that I can open a PR to add. I will definitely add examples and a better description before I do so. Before doing so, I wanted to run it by you guys (especially @krzkaczor) and see if they all seem fine or I should leave any of them behind.

Here is the list with appropriate credits: (If there is no Stackoverflow link associate with something, I have written that myself.)

Functions

export type AnyFunction<T = any> = (...args: any[]) => T;

export type FirstParameter<T extends (...args: any) => any> =
	T extends (first: infer P, ...args: any[]) => any ? P : never;
export type AfterFirstParameters<T extends (...args: any) => any> =
	T 	extends (a0: any) => any ? []
	: T extends (a0: any, a1: infer P1) => any ? [P1]
	: T extends (a0: any, a1: infer P1, a2: infer P2) => any ? [P1, P2]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3) => any ? [P1, P2, P3]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3, a4: infer P4) => any ? [P1, P2, P3, P4]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3, a4: infer P4, a5: infer P5) => any ? [P1, P2, P3, P4, P5]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3, a4: infer P4, a5: infer P5, a6: infer P6) => any ? [P1, P2, P3, P4, P5, P6]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3, a4: infer P4, a5: infer P5, a6: infer P6, a7: infer P7) => any ? [P1, P2, P3, P4, P5, P6, P7]
	: T extends (a0: any, a1: infer P1, a2: infer P2, a3: infer P3, a4: infer P4, a5: infer P5, a6: infer P6, a7: infer P7, a8: infer P8) => any ? [P1, P2, P3, P4, P5, P6, P7, P8]
	: never;

/**
 * ReturnType for Overloaded functions, based on the arguments type (array). It supports up to 9 overloads.
 *
 * Credit: https://stackoverflow.com/a/60822641 by Aideen
 */
export type OverloadReturnTypeWithArgs<T extends (...args: any[]) => any, ARGS_T extends any[]> =
	Extract<
	T 	extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4; (...a: infer A5): infer R5; (...a: infer A6): infer R6; (...a: infer A7): infer R7; (...a: infer A8): infer R8; (...a: infer A9): infer R9 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] | [A5, R5] | [A6, R6] | [A7, R7] | [A8, R8] | [A9, R9]
	: T	extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4; (...a: infer A5): infer R5; (...a: infer A6): infer R6; (...a: infer A7): infer R7; (...a: infer A8): infer R8 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] | [A5, R5] | [A6, R6] | [A7, R7] | [A8, R8]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4; (...a: infer A5): infer R5; (...a: infer A6): infer R6; (...a: infer A7): infer R7 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] | [A5, R5] | [A6, R6] | [A7, R7]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4; (...a: infer A5): infer R5; (...a: infer A6): infer R6 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] | [A5, R5] | [A6, R6]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4; (...a: infer A5): infer R5 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] | [A5, R5]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3; (...a: infer A4): infer R4 } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2; (...a: infer A3): infer R3 } ? [A1, R1] | [A2, R2] | [A3, R3]
	: T extends { (...a: infer A1): infer R1; (...a: infer A2): infer R2 } ? [A1, R1] | [A2, R2]
	: T extends { (...a: infer A1): infer R1 } ? [A1, R1]
	: never,
	[ARGS_T, any]
	>[1];

export type DataTransformer<T, U> = (x: T) => U;

Replace Types

// ~~~~~~~~~~~~~~~~ Replace Types ~~~~~~~~~~~~~~~~
// Needed for Generic Functions Fixation.  Source: https://stackoverflow.com/a/60846777 by Aideen
export type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
export type ReplaceTypeInArray<ARR, F, T> =
	ARR extends [] ? []
	: ARR extends [infer P0] ? [P0 extends F ? T : P0]
	: ARR extends [infer P0, infer P1] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>]
	: ARR extends [infer P0, infer P1, infer P2] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>]
	: ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7, infer P8] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>, ReplaceType<P8, F, T>]
	: never;

Comparison/Union

export type ExactInner<T> = <D>() => (D extends T ? D : D);
export type Exact<T> = ExactInner<T> & T;

// From https://stackoverflow.com/a/53808212
export type IfEquals<T, U, Y=unknown, N=never> =
	(<G>() => G extends T ? 1 : 2) extends
	(<G>() => G extends U ? 1 : 2) ? Y : N;

// From https://stackoverflow.com/a/50375286
export type UnionToIntersection<U>
	= (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// From: https://stackoverflow.com/a/53955431
export type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

Mappings

export type Mapping<T = unknown> = Record<PropertyKey, T>;

export type StringLiteralOnly<T> = (string extends T ? never : string); // https://stackoverflow.com/q/60185084
export type TrueStringLiterals<T extends string> = string extends T ? never : true;

// From: https://stackoverflow.com/a/60807986 by Aideen!
export type SingleKey<T> = IsUnion<keyof T> extends true ? never : {} extends T ? never : T;

// From: https://stackoverflow.com/a/57726844
export type KeyFromValue<T extends Mapping, V>
= { [K in keyof T]: V extends T[K] ? K : never }[keyof T];

// Note that it automatically strips out any value that doesn't extend PropertyKey
export type Invert<T extends Mapping<any>>
	= { [V in T[keyof T]]: KeyFromValue<T, V> };

Constructors

export type Constructor<T = any> = new (...args: any[]) => T;
export type ConstructorFromInstance<T extends Constructor> = Constructor<InstanceType<T>>;
export type AbstractConstructor<T = any> = Function & { prototype: T }; // https://stackoverflow.com/a/38642922

export type GetInPromiseType<T> = T extends Promise<infer P> ? P : never;

PS. If you think I should open separate issues for each (or each specific group), please let me know. Thanks!

@krzkaczor
Copy link
Collaborator

Hey @eyedean,

It's great to have you here!

These types look amazing but I am worried a little bit that we can overwhelm our readme (and our users) if we add all of them. I feel we need better structure for readme soon (folded regions or something).

What if, for starters, we grab just the most useful types from what you presented? Some comments:

Functions

  • AnyFunction I always used just Function for this... what's the difference (other than yours is easier to read I guess).
  • FirstParameter, AfterFirstParameters I feel like there are very specific... I never had a need to use such type. I am not saying it's never useful but probably it's not essential.
  • OverloadReturnTypeWithArgs this looks useful - it seems like it's solving some edgecase of ReturnType
  • DataTransformer I guess it could be renamed as Mapper<IN, OUT>?
  • EnumKeysTransformer I would need to see some examples for this type...

I need more time and will power to dive into the other but some like Exact look very good 😄 We had long outstanding issue for this: #87

@quezak @macbem WDYT?

@eyedean
Copy link
Author

eyedean commented Mar 29, 2020

I'm glad that you found it helpful, Kris.

I definitely agree that Readme needs an overhaul. However, it doesn't need to be anything complicated -- just a grouping (like http://node.cool/) would suffice for now, I guess.

We can probably do the same for the code structure. I took a look at https://github.com/krzkaczor/ts-essentials/blob/master/lib/types.ts and almost 80% of it simply covers deepX stuffs with no relative order as far as I found.

Let me know if I could assist in that, or if you want me to issue my PR after you are done with the Readme/code overhaul.

Thanks!

PS. I agree that some of the cases in my utility types are very specific to my project! I'll leave them out or provide useful examples for them.


PPS. my replies:

AnyFunction I always used just Function for this... what's the difference (other than yours is easier to read I guess).

Function is not a generic. AnyFunction is. AnyFunction<number> is a function that returns a number. I am totally open to a better naming.

FirstParameter, AfterFirstParameters I feel like there are very specific... I never had a need to use such type. I am not saying it's never useful but probably it's not essential.

Agreed. In my case, I have a layer of abstraction that I need to convert function(X, Y, Z) to function(Y,Z). These function come in handy there. So would be if someone wants to play with currying and partial -- https://javascript.info/currying-partials.

Also, I think just having as much of usable stuff as we can provide wouldn't hurt as long as they are organized. In this case, If we have a group of Function and then a subgroup of Function Arguments under that, I would guess these would fit perfectly there.

DataTransformer I guess it could be renamed as Mapper<IN, OUT>?

Sure. I am open to a better/shorter naming.

EnumKeysTransformer I would need to see some examples for this type...

Yup. I am heavily using Enums in my codebases and this is why it was handy for me. The fact that in TypeScript, objects of Enum<string, number> are actually containing both a set of "strings" and a set of "numbers" (to support two-way mapping) is sometimes troublesome, and that's why the Extract<X, number> here was handy.

I removed that from the suggested utilities in my first post anyway.

@quezak
Copy link
Collaborator

quezak commented Mar 30, 2020

I see some of these are useful, but also we could add them in smaller PRs of fewer closely-related types, so it's easier to discuss. For some types I don't see a real usecase right away, it may be easier with a provided simple example and by using named generic args instead of just T :)

Some initial thoughts:

Functions

FirstParameter and AfterFirstParameters may be specific -- but maybe we could add the more general "functional programming classics", Head & Tail? You can easily use those to write your (After|)FirstParameter. In fact, I'll go ahead and make a PR for head & tail now :D (and btw, the second one doesn't need to be written as N exploded examples, see #140)

Union

IsUnion looks related to issue #100, summoning @akwodkiewicz (and UnionToIntersection is already in our lib)

Mappings

  • Your Mapping looks to be exactly our SafeDictionary, isn't it?
  • SingleKey looks to have very limited usecases, but I like it :D
  • KeyFromValue and Invert look nice

Constructors

  • I'd name the first one Type<T>, that's how nest.js does it and I'm already quite used to it. The other types also should be useful.
  • GetInPromiseType is already discussed in Add unwrap promise type  #110 -- btw, it also will be built-in as an awaited keyword, see PR here, it was initially scheduled for TS3.9 but was pushed back (see release notes)

@quezak
Copy link
Collaborator

quezak commented Mar 30, 2020

Some higher level thoughts: @krzkaczor we need to clarify the scope of our lib :) this is named "essentials" after all, so we probably don't want to include too many domain-specific types, like specific higher-order type transformations for functional programming -- I bet there are already specific libraries for that, right? (though we can include the "essential" ones, like Head & Tail are the building blocks of numerous functional utils)

@quezak quezak added the enhancement New feature or request label Mar 30, 2020
@macbem
Copy link
Contributor

macbem commented Mar 30, 2020

ad. Exact<T> - #87

@eyedean
Copy link
Author

eyedean commented Mar 30, 2020

You can easily use those to write your (After|)FirstParameter. In fact, I'll go ahead and make a PR for head & tail now :D (and btw, the second one doesn't need to be written as N exploded examples, see #140)

That was clever! It's one of the biggest benefits of the open-source community -- you'll never stop learning. :)

we need to clarify the scope of our lib :) this is named "essentials" after all, so we probably don't want to include too many domain-specific types, like specific higher-order type transformations for functional programming

I can look at the scope of this lib from 4 different perspectives.

  1. Package Size: These utilities are usually a couple (or at most 10) lines each. So even 500 of them, in terms of byte-size won't hurt anyone.

  2. Performance: The whole TypeScript job is at compile-time, and these are just definitions sitting next to the developers' code. So, it's very hard for me to imagine a developer refusing to install this package because of any impact on his life or their end-users' life.

  3. Ease of Access (for the users of this package: Developers): On the one, I totally agree that we don't want new developers to freak out when they land here! That's why, I agree with @krzkaczor that overhauling the whole structure of this package, both in code and in Readme, is the highest priority.

  4. Maintenance Cost (for contributors and owners): Again, if the package is well-organized, the added cost of new utilities shouldn't even be linear. In fact, one advantage is that it might even attract more people (like myself!) as they find this tool a perfect complement for a lot of their TypeScript Utility Function needs.

@Beraliv
Copy link
Collaborator

Beraliv commented Mar 5, 2023

cc @krzkaczor

These types look amazing but I am worried a little bit that we can overwhelm our readme (and our users) if we add all of them. I feel we need better structure for readme soon (folded regions or something).

I've updated documentation structure in #347

So now we can pick something to implement from here if it's not done yet, let me reread it and I will come back with the plan to resolve this issue

@Beraliv Beraliv added the v10.1 label May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request v10.1
Projects
None yet
Development

No branches or pull requests

5 participants