├── .gitignore ├── LICENSE ├── README.md ├── autocomplete.ts ├── branded_types.ts ├── calculator.ts ├── camelcase.ts ├── console.ts ├── deepreplace.ts ├── fetch.ts ├── find-types.ts ├── infer.ts ├── json-typed.ts ├── length.ts ├── literal_union.ts ├── lodash-fn-pipe.ts ├── mapped_types.ts ├── never.ts ├── one_of.ts ├── promisify.ts ├── recursion.ts ├── shorts └── search-replace.ts ├── template_literals.ts └── todos.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 typed-rocks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typescript Rocks 2 | 3 | This repository is a collection of all the hidden gems I've discovered while using typescript. 4 | -------------------------------------------------------------------------------- /autocomplete.ts: -------------------------------------------------------------------------------- 1 | type AutoComplete = T | (string & {}); 2 | 3 | type Lang = AutoComplete<"TypeScript"| "JavaScript">; 4 | 5 | const valid: Lang = "Anything"; 6 | const alsoValid: Lang = "TypeScript"; 7 | // ^ Autocomplete suggestions from ts-server works 8 | -------------------------------------------------------------------------------- /branded_types.ts: -------------------------------------------------------------------------------- 1 | 2 | declare const __brand: unique symbol; 3 | 4 | type Branded = T & {[__brand]: Brand}; 5 | 6 | const email: string = 'hi@youtube.com'; 7 | 8 | type Email = Branded; 9 | function sendEmail( 10 | address: Email, 11 | text :string 12 | ) 13 | 14 | { 15 | console.log(`send email to ${address} with text ${text}`); 16 | } 17 | 18 | function isValidEmail(input: string): input is Email { 19 | return input.includes('@'); 20 | } 21 | 22 | 23 | function assertValidEmail(input: string): asserts input is Email { 24 | if(!input.includes('@')) { 25 | throw new Error(`${input} is no email`); 26 | } 27 | } 28 | 29 | assertValidEmail(email); 30 | email 31 | // ^? 32 | sendEmail(email, 'our text'); 33 | 34 | 35 | sendEmail('totally not an email', 'asdf'); 36 | -------------------------------------------------------------------------------- /calculator.ts: -------------------------------------------------------------------------------- 1 | type Numbers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; 2 | type Parentheses = '(' | ')'; 3 | type Signs = '+' | '-' | '*' | '/'; 4 | 5 | type AfterNumbers = { [key in Numbers]: Signs | ')' | Numbers | '' }; 6 | type AfterSigns = { [key in Signs]: '(' | Numbers }; 7 | type AfterBrackets = { '(': '(' | Numbers; ')': Signs | ')' | '' }; 8 | type NextAllowed = AfterNumbers & AfterSigns & AfterBrackets; 9 | 10 | type AllAllowed = Numbers | Parentheses | Signs; 11 | 12 | type AllowedStarts = 13 | { [key in Numbers]: Signs | Numbers | '' } & 14 | { '(': '(' | Numbers }; 15 | 16 | type IsEmpty = T extends '' ? true 17 | : T['length'] extends 0 ? true : false; 18 | 19 | type Remove = 20 | T extends `${infer Head}${infer Remaining}` 21 | ? Remove 22 | : Collect; 23 | 24 | type First = 25 | IsEmpty extends true 26 | ? '' : 27 | T extends `${infer Head extends AllAllowed}${string}` ? Head : false; 28 | 29 | type CorrectStart = 30 | T extends `${infer Head extends keyof AllowedStarts}${infer Remaining}` 31 | ? First extends AllowedStarts[Head] 32 | ? true : false 33 | : false; 34 | 35 | type IsNextAllowed = 36 | T extends `${infer Head extends AllAllowed}${infer Remaining}` 37 | ? First extends NextAllowed[Head] 38 | ? IsNextAllowed 39 | : false 40 | : IsEmpty; 41 | 42 | type OnlyBrackets = Remove>; 43 | 44 | type EmptyStringAndEmptyArray = 45 | [IsEmpty, IsEmpty] extends true[] ? true : false; 46 | 47 | type ParenthesesCheck = 48 | T extends `(${infer Remaining}` 49 | ? ParenthesesCheck 50 | : T extends `)${infer Remaining}` 51 | ? Stack extends ['(', ...infer RemainingStack extends Parentheses[]] 52 | ? ParenthesesCheck 53 | : false 54 | : EmptyStringAndEmptyArray; 55 | 56 | type Calculator> = 57 | [ 58 | ParenthesesCheck>, 59 | CorrectStart, 60 | IsNextAllowed] extends true[] 61 | ? T : never; 62 | 63 | function validate(input: Calculator): T { 64 | return input; 65 | } 66 | 67 | const v1 = validate("(1 * (2 + 3))"); 68 | const v2 = validate("3 * (5 + 2) / (4 - 1)"); 69 | const v3 = validate("((7 - 2) * 4) / (3 + 1)"); 70 | const v4 = validate("10 - ((2 + 3) * 4)"); 71 | const v5 = validate("((2 * 3) + 5) / (6 - 1)"); 72 | const v6 = validate("(1 + 2) * 3 - 4 / (5 + 6)"); 73 | const v7 = validate("2 * ((3 + 4) * (5 - 1)) / 6"); 74 | const v8 = validate("((8 / 2) + (7 * 2)) * (9 - 1)"); 75 | const v9 = validate("(2 * (3 + 4) / (5 - 1))"); 76 | const v10 = validate("(10 - 2) / (3 + (5 - 4))"); 77 | 78 | 79 | const e2 = validate("(4 * 6 + 3) / )2 - 1(");//(mismatched brackets) 80 | const e3 = validate("3 * / 2");//(missing operand) 81 | const e4 = validate("(2+3)*(4-)"); //(missing operand) 82 | const e5 = validate("5 + * 3"); // (misplaced operator) 83 | const e6 = validate("((7 - 2) * 4 / (3 + 1)"); // (missing closing bracket) 84 | const e7 = validate("2 * (3 + 4)) * (5 - 1)) / 6"); // (mismatched brackets) 85 | const e8 = validate("((8 / 2) + (7 * 2)) * (9 - 1"); // (missing closing bracket) 86 | const e9 = validate("2 * (3 + 4) / (5 - 1))"); // (mismatched brackets) 87 | const e10 = validate("(10 - 2) / (3 + (5 - 4)) + "); // (missing operand) 88 | -------------------------------------------------------------------------------- /camelcase.ts: -------------------------------------------------------------------------------- 1 | 2 | // hello_youtube 3 | type Separator = '_' | '-'; 4 | 5 | type UppercaseNext = 6 | Check extends true ? Uppercase : T; 7 | 8 | type CamelCase = 9 | T extends `${infer head}${infer tail}` 10 | ? head extends Separator 11 | ? CamelCase 12 | : CamelCase}`> 13 | : Save; 14 | 15 | type SnakeCased = 'hello_youtube'; 16 | 17 | type CamelCased = CamelCase; 18 | // ^? 19 | -------------------------------------------------------------------------------- /console.ts: -------------------------------------------------------------------------------- 1 | 2 | type Mapping = { 3 | [Key in "d" | "i" | "f"]: number 4 | } & 5 | {s: string} & 6 | {[Key in "o" | "O"]: object}; 7 | 8 | type AddMappedToArray = 9 | Char extends keyof Mapping ? [...ArgsArray, Mapping[Char]] : ArgsArray; 10 | 11 | type FirstChar = T extends `${infer Head}${string}` ? Head : T; 12 | 13 | type ArgsFromPlaceholder< 14 | RemainingString extends string, 15 | ArgsArray extends any[] = [] 16 | > = RemainingString extends `${infer Head}${infer Tail}` 17 | ? Head extends "%" 18 | ? ArgsFromPlaceholder>> 19 | : ArgsFromPlaceholder 20 | : [...ArgsArray, ...args: any[]]; 21 | 22 | type BaseTypes = string | number | object | bigint | boolean | symbol | null | undefined; 23 | 24 | type OptionalParams = Input extends string ? ArgsFromPlaceholder : any[]; 25 | 26 | declare var console: Omit & { 27 | log( 28 | message?: T, ...optionalParams: OptionalParams 29 | ): void; 30 | } 31 | 32 | console.log("%s %d, YouTube", "Hi", 5); 33 | 34 | console.log("Hi %d, %s", "Five", "YouTube"); 35 | -------------------------------------------------------------------------------- /deepreplace.ts: -------------------------------------------------------------------------------- 1 | type LegacySystemApi = { 2 | legacy_nameV1: string; 3 | legacy_nameV2: string; 4 | legacy_nameV3: string; 5 | legacy_timstampV1: string; 6 | new_timestampV1: string; 7 | legacy_userV1: { 8 | legacy_uuidV1: number, 9 | legacy_uuid_V2: string, 10 | legacy_firstnameV1: string 11 | } 12 | } 13 | 14 | type OurApi = { 15 | user: { 16 | firstname: string; 17 | id: string; 18 | } 19 | name: string; 20 | timestamp: string; 21 | } 22 | 23 | 24 | type FromTo = { from: string, to: string }; 25 | 26 | type SearchAndReplace = 27 | T extends `${infer Before}${From}${infer After}` 28 | ? SearchAndReplace<`${Before}${To}${After}`, From, To> : T; 29 | 30 | type SearchAndReplaceAll = 31 | FromToArray extends [ 32 | { from: infer From extends string, to: infer To extends string }, 33 | ...infer Remaining extends FromTo[] 34 | ] 35 | ? SearchAndReplaceAll, Remaining> 36 | : T; 37 | 38 | type Replacements = [ 39 | { from: 'legacy_', to: '' }, 40 | { from: 'new_', to: '' }, 41 | { from: `V${number}`, to: '' }, 42 | { from: 'timstamp', to: 'timestamp' }, 43 | { from: 'uuid_', to: 'id' }, 44 | { from: 'uuid', to: 'id' }, 45 | ] 46 | 47 | 48 | type DeepReplace = { 49 | [Key in keyof T as SearchAndReplaceAll]: DeepReplace 50 | } 51 | 52 | type Cleaned = DeepReplace< 53 | LegacySystemApi, Replacements 54 | >; 55 | const clean: Cleaned = { 56 | user: { 57 | firstname: 'Christian', 58 | id: '1234' 59 | }, 60 | name: "No more Legacy", 61 | timestamp: '1111' 62 | } 63 | -------------------------------------------------------------------------------- /fetch.ts: -------------------------------------------------------------------------------- 1 | 2 | type Todo = {userId: string, title: string, completed: boolean}; 3 | 4 | const apiUrl = 'any url'; 5 | 6 | //Issues 7 | // 1. No Generic Responsetype 8 | // 2. Typesafe method 9 | // 3. Body not always allowed 10 | // 4. type safe headers 11 | const resp = fetch(apiUrl, { 12 | method: 'PUT', 13 | body: 'asdf', 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | 'Accept': 'application/json', 17 | } 18 | }) 19 | .then(resp => resp.json()); 20 | 21 | type Type = string; 22 | 23 | const a = 24 | 25 | fetch(apiUrl) 26 | .then(r => r.json()) 27 | // ^? Promise 28 | 29 | type TypedHeaders = RequestInit['headers'] & PreparedHeaders; 30 | 31 | type PreparedHeaders = Partial<{ 32 | 'Content-Type': MimeTypes, 33 | 'Accept': MimeTypes, 34 | 'Authorization': `Bearer ${string}` 35 | }>; 36 | 37 | declare function fetch( 38 | input: RequestInfo | URL, init?: TypedRequestInit 39 | ): Promise>; 40 | 41 | type HttpVerbs = 'POST' | 'PUT' | 'DELETE' | 'UPDATE' | 'GET' | 'CONNECT' | 'HEAD' | 42 | 'OPTIONS'; 43 | 44 | type WithBody = Extract; 45 | type NonBody = Exclude; 46 | 47 | type MethodBodyCombination = {method?: WithBody, body?: RequestInit['body']} | 48 | {method?: NonBody, body?: never} 49 | 50 | type TypedRequestInit = RequestInit & MethodBodyCombination & {headers?: TypedHeaders}; 51 | 52 | 53 | 54 | interface TypedResponse extends Response { 55 | json(): Promise; 56 | } 57 | 58 | type MimeTypes = 59 | ".jpg" | 60 | ".midi" | 61 | "XML" | 62 | "application/epub+zip" | 63 | "application/gzip" | 64 | "application/java-archive" | 65 | "application/json" | 66 | "application/ld+json" | 67 | "application/msword" | 68 | "application/octet-stream" | 69 | "application/ogg" | 70 | "application/pdf" | 71 | "application/php" | 72 | "application/rtf" | 73 | "application/vnd.amazon.ebook" | 74 | "application/vnd.apple.installer+xml" | 75 | "application/vnd.mozilla.xul+xml" | 76 | "application/vnd.ms-excel" | 77 | "application/vnd.ms-fontobject" | 78 | "application/vnd.ms-powerpoint" | 79 | "application/vnd.oasis.opendocument.presentation" | 80 | "application/vnd.oasis.opendocument.spreadsheet" | 81 | "application/vnd.oasis.opendocument.text" | 82 | "application/vnd.openxmlformats-officedocument.presentationml.presentation" | 83 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | 84 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | 85 | "application/vnd.rar" | 86 | "application/vnd.visio" | 87 | "application/x-abiword" | 88 | "application/x-bzip" | 89 | "application/x-bzip2" | 90 | "application/x-csh" | 91 | "application/x-freearc" | 92 | "application/x-sh" | 93 | "application/x-shockwave-flash" | 94 | "application/x-tar" | 95 | "application/x-7z-compressed" | 96 | "application/xhtml+xml" | 97 | "application/zip" | 98 | "audio/aac" | 99 | "audio/mpeg" | 100 | "audio/ogg" | 101 | "audio/opus" | 102 | "audio/wav" | 103 | "audio/webm" | 104 | "font/otf" | 105 | "font/ttf" | 106 | "font/woff" | 107 | "font/woff2" | 108 | "image/bmp" | 109 | "image/gif" | 110 | "image/png" | 111 | "image/svg+xml" | 112 | "image/tiff" | 113 | "image/vnd.microsoft.icon" | 114 | "image/webp" | 115 | "text/calendar" | 116 | "text/css" | 117 | "text/csv" | 118 | "text/html" | 119 | "text/javascript" | 120 | "text/plain" | 121 | "video/3gpp" | 122 | "video/3gpp2" | 123 | "video/mp2t" | 124 | "video/mpeg" | 125 | "video/ogg" | 126 | "video/webm" | 127 | "video/x-msvideo"; 128 | 129 | -------------------------------------------------------------------------------- /find-types.ts: -------------------------------------------------------------------------------- 1 | type Account = { 2 | id: ComplexId; 3 | login: string; 4 | }; 5 | 6 | type FileData = { 7 | id: number; 8 | blob: string; 9 | }; 10 | 11 | type ComplexId = { 12 | mainId: boolean; 13 | id: Date; 14 | }; 15 | 16 | type Person = { 17 | id: string; 18 | firstname: string; 19 | lastname: string; 20 | fileData: FileData; 21 | account: Account; 22 | }; 23 | 24 | 25 | type FindAllTypesForKey = 26 | SearchIn extends object 27 | ? (KeyToLookFor extends keyof SearchIn ? SearchIn[KeyToLookFor]: never) 28 | | { 29 | [P in keyof SearchIn] : FindAllTypesForKey 30 | }[keyof SearchIn] 31 | : never; 32 | 33 | type Id = FindAllTypesForKey; 34 | // ^? 35 | -------------------------------------------------------------------------------- /infer.ts: -------------------------------------------------------------------------------- 1 | type MyReturnType = T extends (...args: any[]) => infer R ? R : never; 2 | 3 | function add(a: number, b: number): number { 4 | return a + b; 5 | } 6 | 7 | type AddReturnType = MyReturnType; 8 | // ^? 9 | 10 | 11 | type RgbInfer = T extends `rgb(${infer first},${infer second},${infer third})` 12 | ? [first, second, third] : never; 13 | 14 | type RgbInferNumber = T extends `rgb(${infer first extends number},${infer second extends number},${infer third extends number})` 15 | ? [first, second, third] : never; 16 | 17 | type ValidRgb = RgbInfer<'rgb(1,2,3)'>; 18 | // ^? 19 | type ValidRgbNumber = RgbInferNumber<'rgb(1,2,3)'>; 20 | // ^? 21 | 22 | 23 | type InvalidRgb = RgbInferNumber<'rgb(1,2,a)'>; 24 | // ^? 25 | 26 | -------------------------------------------------------------------------------- /json-typed.ts: -------------------------------------------------------------------------------- 1 | 2 | const obj = { 3 | a: 'hello', 4 | b: 1, 5 | c: undefined, 6 | d: { 7 | toJSON() { 8 | return 42; 9 | } 10 | }, 11 | e: () => console.log('hi from e') 12 | } 13 | 14 | const str = JSON.stringify(obj);//? 15 | // ^? 16 | 17 | const parsed = JSON.parse(str); 18 | // ^? 19 | 20 | writePersonObject(''); 21 | 22 | 23 | function writePersonObject(str: Stringified<{firstname: string, lastname: string}>) { 24 | 25 | } 26 | 27 | type JsonifiedValue = T extends string | number | null | boolean 28 | ? T 29 | : T extends {toJSON(): infer R} ? R 30 | : T extends undefined | ((...args: any[]) => any) ? never 31 | : T extends object ? JsonifiedObject 32 | : never; 33 | 34 | type JsonifiedObject = { 35 | [Key in keyof T as [JsonifiedValue] extends [never] ? never : Key]: JsonifiedValue 36 | } 37 | 38 | parsed.b 39 | 40 | type Stringified = string & {source: ObjType}; 41 | 42 | interface JSON { 43 | stringify(value: T, replacer?: null | undefined, space?: string | number): Stringified; 44 | parse(str: Stringified, replacer?: null | undefined): JsonifiedObject; 45 | } 46 | -------------------------------------------------------------------------------- /length.ts: -------------------------------------------------------------------------------- 1 | 2 | type Length = 3 | T extends `${string}${infer Tail}` 4 | ? Length 5 | : Counter['length']; 6 | 7 | // Compare<1,2> 8 | type Compare = 9 | First extends Second 10 | ? 'equal' 11 | : Counter['length'] extends First 12 | ? 'less' 13 | : Counter['length'] extends Second 14 | ? 'greater' 15 | : Compare; 16 | 17 | type MaxLength = 18 | Compare, Max> extends 'less' | 'equal' ? T : never; 19 | 20 | type MinLength = 21 | Compare> extends 'less' | 'equal' ? T : never; 22 | 23 | type InRange = 24 | MinLength & MaxLength; 25 | 26 | function maxOrThrow( 27 | str: MaxLength, max: Max 28 | ): string { 29 | if(str.length > max) { 30 | throw new Error(`${str} is longer than ${max}`); 31 | } 32 | 33 | return str; 34 | } 35 | 36 | function minOrThrow( 37 | str: MinLength, min: Min 38 | ): string { 39 | if(str.length > min) { 40 | throw new Error(`${str} is shorter than ${min}`); 41 | } 42 | 43 | return str; 44 | } 45 | 46 | 47 | function exactOrThrow( 48 | str: MinLength, exact: Exact 49 | ): string { 50 | if(str.length !== exact) { 51 | throw new Error(`${str} is not exact ${exact}`); 52 | } 53 | 54 | return str; 55 | } 56 | 57 | function inRangeOrThrow( 58 | str: InRange, min: Min, max: Max 59 | ): string { 60 | if(str.length > max) { 61 | throw new Error(`${str} is longer than ${max}`); 62 | } else if(str.length < min) { 63 | throw new Error(`${str} is shorter than ${min}`); 64 | } 65 | 66 | return str; 67 | } 68 | 69 | 70 | 71 | const a = 'Test' 72 | 73 | inRangeOrThrow(a, 1, 4); 74 | -------------------------------------------------------------------------------- /literal_union.ts: -------------------------------------------------------------------------------- 1 | type LiteralUnion = Suggestions | (string & {}); 2 | 3 | type ImageMimiTypes = 4 | "image/bmp" | 5 | "image/gif" | 6 | "image/png" | 7 | "image/svg+xml" | 8 | "image/tiff" | 9 | "image/vnd.microsoft.icon" | 10 | "image/webp"; 11 | 12 | let validValueWithMimeType: LiteralUnion = 'image/bmp'; // this is valid and was suggested automatically 13 | let validValueWithoutMimeType: LiteralUnion = ''; // this is still valid 14 | -------------------------------------------------------------------------------- /lodash-fn-pipe.ts: -------------------------------------------------------------------------------- 1 | type PipeFn = (...args: any[]) => any; 2 | 3 | type PipeChain = RemainingFns extends [ 4 | infer First extends PipeFn, 5 | infer Second extends PipeFn, 6 | ...infer Rem extends PipeFn[] 7 | ] 8 | ? PipeChain<[(...args: [ReturnType]) => ReturnType, ...Rem], [...CollectedFns, First]> 9 | : [...CollectedFns, ...RemainingFns]; 10 | 11 | type LastFn = Fns extends [...PipeFn[], infer Last extends PipeFn] ? Last : Fns[0]; 12 | 13 | function typedPipe(...args: PipeChain) { 14 | return fp.pipe(args) as (...args: Parameters) => ReturnType>; 15 | } 16 | const a = (a: string) => ({a: 'a', b: 1}); 17 | const b = (c: {a: string, b: number}) => true; 18 | const c = (b: boolean) => new Date(); 19 | const p = typedPipe(a, b, c); 20 | // 21 | -------------------------------------------------------------------------------- /mapped_types.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | type Events = { 4 | add: string; 5 | delete: string; 6 | move: string; 7 | } 8 | 9 | const userActions: On = { 10 | onAdd: () => {}, 11 | onDelete: () => {}, 12 | onMove: () => {} 13 | } 14 | 15 | type On = { 16 | [Key in keyof T as Key extends string ? 17 | `on${Capitalize}` : never]: () => any 18 | } 19 | -------------------------------------------------------------------------------- /never.ts: -------------------------------------------------------------------------------- 1 | function error(): never { 2 | throw new Error('never returns'); 3 | } 4 | 5 | function neverDone(): never { 6 | while(true) { 7 | } 8 | } 9 | 10 | // without never 11 | function error() { 12 | throw new Error('any error'); 13 | } 14 | let possiblyNull: string | null = '' as any; 15 | if(!possiblyNull) error(); 16 | possiblyNull; 17 | // ^? let possiblyNull: string | null 18 | 19 | // with never 20 | function error(): never { 21 | throw new Error('any error'); 22 | } 23 | let possiblyNull: string | null = '' as any; 24 | if(!possiblyNull) error(); 25 | possiblyNull; 26 | // ^? let possiblyNull: string 27 | 28 | // or 29 | const nonNull = possiblyNull ?? error(); 30 | 31 | function unknownLanguages(lang: never): never { 32 | throw new Error(`${lang} not known`); 33 | } 34 | type Languages = 'js' | 'ts'; 35 | 36 | function getRealName(language: Languages): string { 37 | switch(language) { 38 | case 'js': 39 | return 'JavaScript'; 40 | case 'ts': 41 | return 'TypeScript'; 42 | default: 43 | return unknownLanguages(language); 44 | } 45 | } 46 | 47 | type ImageMessage = { 48 | imgPath: string; 49 | text?: never; 50 | } 51 | 52 | type TextMessage = { 53 | text: string; 54 | imgPath?: never; 55 | } 56 | 57 | type Message = {timestamp: number, sender: string} & (ImageMessage | TextMessage); 58 | 59 | const m: Message = { 60 | imgPath: '/path/img', 61 | sender: 'Christian Worz', 62 | text: 'A text', 63 | timestamp: 123 64 | } 65 | 66 | type Filter = { 67 | [Key in keyof Obj 68 | as ValueType extends Obj[Key] ? Key : never] 69 | : Obj[Key] 70 | } 71 | 72 | type IsNever = T extends never ? true : false; 73 | 74 | type R = IsNever; 75 | // ^? never => never is an empty union type so nothing to check for to extend. so its never again 76 | 77 | // Fix: 78 | 79 | type IsNever = [T] extends [never] ? true : false; 80 | 81 | type R = IsNever; 82 | // ^? 83 | -------------------------------------------------------------------------------- /one_of.ts: -------------------------------------------------------------------------------- 1 | 2 | type BaseMessage = { id: string, timestamp: number }; 3 | type TextMessage = BaseMessage & { text: string; }; 4 | type ImgMessage = BaseMessage & { imgPath: string }; 5 | type UrlMessage = BaseMessage & { url: string; }; 6 | 7 | type Message = TextMessage | UrlMessage | ImgMessage; 8 | type MessageTypesArray = OneOf<[TextMessage, UrlMessage, ImgMessage]>; 9 | 10 | type MergeTypes = 11 | TypesArray extends [infer Head, ...infer Rem] 12 | ? MergeTypes 13 | : Res; 14 | 15 | type OneOf< 16 | TypesArray extends any[], 17 | Res = never, 18 | AllProperties = MergeTypes> = 19 | TypesArray extends [infer Head, ...infer Rem] 20 | ? OneOf, AllProperties> 21 | : Res; 22 | 23 | type SimpleOneOf = OnlyFirst | OnlyFirst; 24 | 25 | type OnlyFirst = F & {[Key in keyof Omit]?: never}; 26 | 27 | const message: MessageTypesArray = { 28 | id: '1', 29 | timestamp: new Date().getTime(), 30 | 31 | imgPath: 'path' 32 | } 33 | -------------------------------------------------------------------------------- /promisify.ts: -------------------------------------------------------------------------------- 1 | type Callback = ((err?: any) => void) | ((err: any, result: any) => void); 2 | type GetResult = 3 | Parameters["length"] extends 2 ? Parameters[1] : void; 4 | 5 | type Promisified = 6 | Fn extends (...args: [...infer Args, infer Cb extends Callback]) => void 7 | ? (...args: Args) => Promise> 8 | : never; 9 | 10 | 11 | function typedPromisify(fn: Fn): Promisified { 12 | return promisify(fn) as Promisified; 13 | } 14 | -------------------------------------------------------------------------------- /recursion.ts: -------------------------------------------------------------------------------- 1 | // builtin type: Awaited 2 | 3 | type Resolved = Awaited>>>; 4 | // ^? 5 | 6 | type Tupel = Tupel['length'] extends Length ? Tupel : TupelSized; 7 | 8 | 9 | type T3 = Tupel; 10 | // ^? 11 | 12 | // MAX depth 1000 13 | 14 | interface Form { 15 | a: string; 16 | b: { 17 | c: { 18 | d: string; 19 | } 20 | } 21 | } 22 | 23 | type ArrayOrNever = T extends any[] ? T : never; 24 | 25 | type Path = T extends object ? {[Key in keyof T]: [Key] | [Key, ...Path]}[keyof T] : never; 26 | type R = Path
; 27 | // ^? 28 | -------------------------------------------------------------------------------- /shorts/search-replace.ts: -------------------------------------------------------------------------------- 1 | type Rating = { 2 | aBadLanguage: 'TypeScript', // FROM 3 | //aCoolLanguage: 'TypeScript', // TO 4 | } 5 | 6 | type Replace< 7 | InputStr, 8 | From extends string, 9 | To extends string 10 | > = InputStr extends 11 | `${infer Before}${From}${infer After}` 12 | ? `${Before}${To}${After}` 13 | : InputStr; 14 | 15 | type ReplaceInObject = { 16 | [Key in keyof Obj as 17 | Replace]: Obj[Key] 18 | } 19 | 20 | type Check = ReplaceInObject; 21 | // ^? 22 | -------------------------------------------------------------------------------- /template_literals.ts: -------------------------------------------------------------------------------- 1 | type ChessLetters = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H'; 2 | type ChessNumbers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; 3 | 4 | type BoardPositions = `${ChessLetters}${ChessNumbers}`[]; 5 | 6 | const b: BoardPositions = ['A1']; 7 | 8 | 9 | type RgbCss = `rgb(${number},${number},${number})`; 10 | type RgbaCss = `rgb(${number},${number},${number}${`,${number}` | ''})`; 11 | 12 | const rgbCss: RgbCss = 'rgb(1,2,3)'; 13 | const rgbaCss: RgbaCss = 'rgb(1,2,3,2)'; 14 | 15 | 16 | type GapType = 'margin' | 'padding'; 17 | type GapPosition = 'left' | 'right' | 'top' | 'bottom'; 18 | type GapCss = GapType | `${GapType}-${GapPosition}`; 19 | // ^? 20 | 21 | type SizeType = 'rem' | 'px'; 22 | type SizeCss = `${number}${SizeType}`; 23 | 24 | type MarginPadding = { 25 | [Key in GapCss]?: SizeCss 26 | } 27 | 28 | const margin: MarginPadding = { 29 | "margin-left": '1rem' 30 | } 31 | 32 | type FirstChar = T extends `${infer head}${infer tail}` ? head : ''; 33 | type StartsWithNumber = T extends `${infer head extends number}${infer tail}` ? head : ''; 34 | 35 | type First = FirstChar<'hello youtube'>; 36 | // ^? 37 | 38 | type Starts = StartsWithNumber<'1asdf'>; 39 | // ^? 40 | -------------------------------------------------------------------------------- /todos.ts: -------------------------------------------------------------------------------- 1 | 2 | type Position = [number, number, string]; 3 | type isLength = T['length'] extends Length ? true : false; 4 | 5 | type Inc = [0, ...T]; 6 | 7 | type InitCount = Count['length'] extends Length ? Count : InitCount; 8 | 9 | type WriteHorizontal = Str extends `${infer Head extends string}${infer Rest extends string}` 10 | ? WriteHorizontal, YStart, Rest, [...WriteArray, [XStart['length'], YStart, Head]]> 11 | : WriteArray; 12 | 13 | type WriteVertical = Str extends `${infer Head extends string}${infer Rest extends string}` 14 | ? WriteVertical, XStart, Rest, [...WriteArray, [XStart, YStart['length'], Head]]> 15 | : WriteArray; 16 | 17 | 18 | type WriteWord = WriteHorizontal, YStart, Str>; 19 | 20 | type CheckBox = T extends 1 21 | ? '✅' : T extends 0 ? '🔲' : '🔥'; 22 | 23 | type Todo< done extends number, Todo extends string, Line extends number> = WriteWord<1, Line, `${CheckBox} ${Todo}`>; 24 | 25 | 26 | type GetPositionMatching = 27 | Positions extends [[infer X extends number, infer Y extends number, infer Str extends string], ...infer Rest extends Position[]] 28 | ? XSearch extends X 29 | ? YSearch extends Y 30 | ? [X, Y, Str] 31 | : GetPositionMatching 32 | : GetPositionMatching 33 | : false; 34 | 35 | type DrawAt = 36 | isLength extends true 37 | ? isLength extends true 38 | ? R 39 | : DrawAt`, [], Inc> 40 | : GetPositionMatching extends [number, number, infer Str extends string] 41 | ? DrawAt, YCount> 42 | : DrawAt, YCount>; 43 | 44 | type Repeat = Count['length'] extends Times 45 | ? Word 46 | : Repeat; 47 | type HorizontalLine = 48 | WriteHorizontal, YStart, Repeat<'-', Length>>; 49 | type VerticalLine = 50 | WriteVertical, XStart, Repeat<'|', Length>>; 51 | 52 | 53 | 54 | type block = [ 55 | ...HorizontalLine<0, 0, 23>, 56 | ...HorizontalLine<0, 2, 25>, 57 | ...HorizontalLine<0, 16, 25>, 58 | ...VerticalLine<22, 1, 1>, 59 | ...VerticalLine<0, 1, 1>, 60 | ]; 61 | 62 | 63 | type Todos = [ 64 | ...WriteWord<8, 1, 'Agenda'>, 65 | ...Todo, 66 | ...Todo, 67 | ...Todo, 68 | ...Todo, 69 | ...Todo, 70 | ...Todo, 71 | ] 72 | 73 | type State = [0,1,1,1,1,1]; 74 | type Result = DrawAt<[...block, ...Todos], 23, 16, ' '>; --------------------------------------------------------------------------------