%% generate tags start %% #software-engineering %% generate tags end %% #software-engineering/typescript > [!info] see more > [hotscript/README.md at main · gvergnaud/hotscript (github.com)](https://github.com/gvergnaud/hotscript/blob/main/README.md) > you might also want to check out [ts-essentials/ts-essentials: All basic TypeScript types in one place 🤙 (github.com)](https://github.com/ts-essentials/ts-essentials) # Higher-Order TypeScript (HOTScript) A library of composable functions for the type level! Transform your TypeScript types in any way you want using functions you already know. ![image](https://user-images.githubusercontent.com/9265418/223854503-b54f6a62-9f21-4953-aaa3-5d54699516a7.png) ## Features - Type-level higher-order functions (`Tuples.Map`, `Tuples.Filter`, `Objects.MapValues`, etc). - Type-level pattern matching with `Match`. - Performant math operations (`Numbers.Add`, `Numbers.Sub`, `Numbers.Mul`, `Numbers.Div`, etc). - Custom "lambda" functions. 🚧 work in progress 🚧 ## Installation You can find HotScript on npm: ```ts npm install -D hotscript ``` HotScript is a work-in-progress library, so expect **breaking changes** in its API. ## Examples #### Transforming a list [Run this as a TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgBWGApgGjgFQK5gA26AztgMoxTAB2A5mXAHJ4gBG6UJcAvnAGZQIIOACIAFhBgkAxtTAxRAbgBQKmAE8McKKQCMcALwo06ADwq4cAPTWrAPQD8cAGwAmS3ADae7G+wAzNgALAC6mJ5enlb4RKQAdACyAIZgZizsnCTxAIIAJnlmAQB8xdhWFTZ2XsHYAKzYLtgA7KHRuATE2QBSELRmovGiZZWjY1a2YsHxdfEu8c2i7ZTU9NnkRMAwA0Mj4+OTXqLBotiidadiLpeii22VsV1JqWYrtAzxyLoYNIWiesMRod-icznoLmDrmC7u1HgkUmk3mt4jgIBkOFA9hVDnpanBwdg9E18a1YZ0EuRWPtqQc7O5PG1ikogA) <!-- prettier-ignore --> ```ts import { Pipe, Tuples, Strings, Numbers } from "hotscript"; type res1 = Pipe< // ^? 62 [1, 2, 3, 4], [ Tuples.Map<Numbers.Add<3>>, // [4, 5, 6, 7] Tuples.Join<".">, // "4.5.6.7" Strings.Split<".">, // ["4", "5", "6", "7"] Tuples.Map<Strings.Prepend<"1">>, // ["14", "15", "16", "17"] Tuples.Map<Strings.ToNumber>, // [14, 15, 16, 17] Tuples.Sum // 62 ] >; ``` #### Defining a first-class function [Run this as a TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgYQIYBt0Bo4DEB2OAKgK5joCmAznAL5wBmUEIcARABYQxUDGUwMDDYBuAFBiA9JLhEOwGgrio4MAJ5gKAWkoA3CunbpUIAEYATVGwCEY4PhgUoDVLwpwAImXTBeqR3AUAB6O+OY0BIhicHBQFDAkUPgAXHAA2jDyVGlsqFAA5gAMbAC6OJkKOXlFpSXitBLqmrHUJOgwAIxwALwoGOgAPKTk1AB0ALKoYANe5L7+FAB8OGkdOABMOADMOAAsJYvi0jEnAHoA-I0a7nFUbTDrPX2YQ95juMYwk9OzPn6Oy3SazgmzgOzg+0OUhkJzgFyAA) ```ts import { Call, Fn, Tuples } from "hotscript"; // This is a type-level "lambda"! interface Duplicate extends Fn { return: [this["arg0"], this["arg0"]]; } type result1 = Call<Tuples.Map<Duplicate>, [1, 2, 3, 4]>; // ^? [[1, 1], [2, 2], [3, 3], [4, 4]] type result2 = Call<Tuples.FlatMap<Duplicate>, [1, 2, 3, 4]>; // ^? [1, 1, 2, 2, 3, 3, 4, 4] ``` #### Transforming an object type [Run this as a TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgBWGApgGjgeQEYBW6AxjAM7YBCEEANugIYB2ZcAvnAGZQQhwBEACwjliUNDH4BuAFAyA9PLgAZdDADkrYr0hl0cMr32cArk1LAILODAg2ozMp2h9mcCIRLwYATwwAuGV8MOAAVCABBZABJZAYfWggGABMAHlCAPjgAXhQ0dFSZODDMIrgAbTLi-CJSMgA6HBBgGEofVOo6RhZ6gFEARxMGWjJUsh8QPDoMjNLi6s86+oiyMmAAcyZUpBA1FIYYBn9EOCZ0AHcAVT0oY5goE30ONlmq3EXyeoBlJgYAa3QAGEGHoACLodBgObzGpeBorNabbZwYDJY5ke7AJjrdgZMoAXRkGVkwX0UWi13QUByYUiMTiCSSaQQZUU8wAegB+Mqo9ETKa0WTFTjAKAYgByDF26Mx2KFcFoIJgkulBll61kL1kbIASmoTFAWIFSWEaSziry1eI5WVdodkgcjiczucAPomG53B5PeUisUwV2-VUY60asqKjGBqXoGWhzVSIA) ```ts import { Pipe, Objects, Booleans } from "hotscript"; // Let's compose some functions to transform an object type: type ToAPIPayload<T> = Pipe< T, [ Objects.OmitBy<Booleans.Equals<symbol>>, Objects.Assign<{ metadata: { newUser: true } }>, Objects.SnakeCaseDeep, Objects.Assign<{ id: string }> ] >; type T = ToAPIPayload<{ id: symbol; firstName: string; lastName: string; }>; // Returns: type T = { id: string; metadata: { new_user: true }; first_name: string; last_name: string; }; ``` #### Parsing a route path [Run this as a TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgBWGApgGjgeQEYBW6AxjAM7YDKMUwAdgOYVwDCE4EZ6AMugGYxsAFQCuYADbpmAWQCGMYgAs4AXzh8o7OACJFEcsVpgY2gNwAoczACeGOFCkBWOAF4UadAB5zcOAHo-X18APQB+Hx0-ES4oMj9PYAATAC4yGnoGAD4-SDS4hLpE9AAPZLoREDx0KEztTAiAbQjfalpGMgA6SglgGE9tP21M+qC4UQkpDoAxYHEYas9WjM7qWVgyAHVexX7+zOHmsbFJTrkwb1HfNg4uXgFPJsugpfaOoVoQXe04AB8dWoOTxa6Ve3XEvX6ySGI0uAF1ModAUFxic3hAAKp0YAQOgw3z4IikTpTTQgACidHSUjxuEIJHIHTOADVZOIRFILpc5Aodo8gdylB0tjAdto0m0GHU4OKMkingLFELtv1ypVqlLVVUaodfPDERFYeZMqYgA) https://user-images.githubusercontent.com/2315749/222081717-96217cd2-ac89-4e06-a942-17fbda717cd2.mp4 ```ts import { Pipe, Objects, Strings, ComposeLeft, Tuples, Match } from "hotscript"; type res5 = Pipe< // ^? { id: string, index: number } "/users/<id:string>/posts/<index:number>", [ Strings.Split<"/">, Tuples.Filter<Strings.StartsWith<"<">>, Tuples.Map<ComposeLeft<[Strings.Trim<"<" | ">">, Strings.Split<":">]>>, Tuples.ToUnion, Objects.FromEntries, Objects.MapValues< Match<[Match.With<"string", string>, Match.With<"number", number>]> > ] >; ``` ### Make querySelector typesafe [Run this as a TypeScript Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wBJwGZQiOAcgAsIZUBjKYMGQgKHoBMBTCgG2ShZwFcA7CjGAR+cAI68WUAJ4BlFuzYxoAHgAqcFgA8YLfk3SoY1fgHMAfAApUi5dABccdQEonAUSUh9MAGJ4QBSUhNXULOAAfOH5ednZGYH49KGxkCh51agIdPQN0DAA6XzEAb3o4CrhuGF4ofgdyyphiYFQAbQAiLjMABg6AXS1dfUM4AAMAEhLE7Gk4AAVuADcAXzgAGimZuYA5HJWxuAB+RorCiY0s9fGpxZZVzZK93QPwhsq4ZtbO7r7BnJG6Em034sygC2WK2uW1Bu32hxOH3Ol1A12BdweU2eMFecHeTRa7S6UF6AyGuVGwO24IxKwcrSsMLBcE8KxcTLmACUWMYDsdTpgChdMqibiVaVNWVNubyxm8BV8ib8yQC8mLqRD7nSAO7EaQsRkg5ms9lGrk8nEIgXIkUgNG3SGS9graUW3H4iqKn4kv7kwHq2Hg1mG4ymMxsjngmWW-lIoUou1iqUlaPuhWE72k-7DNVUwMs51tKahxLh-qRuCpq1x4VXJPO12y+UfL3ErP0FaMGAyMA8TwsbxJfz4IL2KAacIAXkF8xoLA01zaAtt62tBTkJlLqHXYHYwBgqkIRAsq7j6l4u55BQAMmgYKfKoUN2Ht3Jd-vD+tCCe1+fL9v1AgABVfgRH4B8znXTdzFfd8Dw6BwOkiOAOk6ZCOgKJCog6ABiDofzPC8lG3ABBA8egIx8CgAWWQGAKGIVQlw+ONaPo4gCgAdX3RiAGsWBkCBsEwdRqOvftBxgdRkDMHZkG8WiwGuQoAHkACMACtlG3ABxFgD0KAB9ZTRPErwfGk2T5JYRSLEoljBTYhiuJ41RkH4GQTLEiSfAsAV+j8gLGHoChRGMKoeR6OBp0kaR5DsEIoCsDoEA6FwAG56AAeiyliAD0TlC-hwu4VAAEZookKRZFHRLkrANLMpy-LCrC+BSoAJkq2KaoSlQko6JhgCWAoODQVA5O8Rrstyj4CpCtqItQABmbrqvi4J+uSoaljgcJkGm5q5ta4r2p5AAWNa4tqraOjANo6JMfpDtmyp5qKkqeQAViu3rNugbbhrgMACj0XQUDaYhuGwSdCBwwhnoymaWoW06loANl+jax2SnDgCYF6UY+s7UAAdixm6AcG4aHpgJ6HGINArDAPa4FDUQzBcQnjtRz7UAADgpvqqfpVArB22mnuuMAuaRo63pOvmAE4hf+gaJceqB+mlyWtYZpmWfCdnzFlprXoqd7FtKsqopi9bKfVoG3kZsXkFZ0WrDU64KBcWXkZ54L6CAA) ```ts import * as H from 'hotscript' declare function querySelector<T extends string>(selector: T): ElementFromSelector<T> | null interface Trim extends H.Fn { return: this["arg0"] extends `${infer Prev} ,${infer Next}` ? H.lt;Trim, `${Prev},${Next}`> : this["arg0"] extends `${infer Prev}, ${infer Next}` ? H.lt;Trim, `${Prev},${Next}`> : this["arg0"] extends `${infer Prev}:is(${infer El})${infer Rest}` ? H.lt;Trim, `${Prev}${El}${Rest}`> : this["arg0"] extends `${infer Prev}:where(${infer El})${infer Rest}` ? H.lt;Trim, `${Prev}${El}${Rest}`> : this["arg0"] extends `${infer El}(${string})${infer Rest}` ? H.lt;Trim, `${El}${Rest}`> : this["arg0"] extends `${infer El}[${string}]${infer Rest}` ? H.lt;Trim, `${El}${Rest}`> : this["arg0"] } type ElementFromSelector<T> = H.Pipe<T, [ Trim, H.Strings.Split<' '>, H.Tuples.Last, H.Strings.Split<','>, H.Tuples.ToUnion, H.Strings.Split<":" | "[" | "." | "#">, H.Tuples.At<0>, H.Match<[ H.Match.With<keyof HTMLElementTagNameMap, H.Objects.Get<H._, HTMLElementTagNameMap>>, H.Match.With<any, HTMLElement> ]> ]> ``` ![image](https://github.com/gvergnaud/hotscript/assets/633115/d75fe58e-e677-4ead-9cd2-b82a32d0cec7)