├── .github └── dependabot.yml ├── .gitignore ├── README.md ├── converter └── convert.ts ├── docker-compose.yml ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _includes │ └── google-analytics.html ├── assets │ └── css │ │ └── style.scss ├── content │ ├── jest.md │ ├── lambda.md │ ├── lint-format.md │ ├── modules.md │ ├── nodejs-lib.md │ ├── project.md │ ├── tsdocs.md │ ├── typescript.md │ └── useful-libraries.md ├── index.md ├── pages │ ├── index.md │ ├── language │ │ ├── async-await.md │ │ ├── basic-types.md │ │ ├── callbacks.md │ │ ├── classes.md │ │ ├── dest-spread.md │ │ ├── function.md │ │ ├── generics.md │ │ ├── iterate.md │ │ ├── promise.md │ │ ├── type-interface.md │ │ ├── utility-types.md │ │ └── variables.md │ └── topics │ │ ├── array-async.md │ │ ├── errors.md │ │ ├── replace-class.md │ │ └── union-enum.md └── privacy.md ├── package.json ├── scripts ├── clean.sh └── markdown.sh ├── src ├── language │ ├── async-await.ts │ ├── basic-types.ts │ ├── callbacks.ts │ ├── classes.ts │ ├── dest-spread.ts │ ├── function.ts │ ├── generics.ts │ ├── iterate.ts │ ├── promise.ts │ ├── type-interface.ts │ ├── utility-types.ts │ └── variables.ts └── topics │ ├── array-async.ts │ ├── errors.ts │ ├── replace-class.ts │ └── union-enum.ts ├── tsconfig.json └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | _site 4 | .jekyll-cache 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-practices 2 | 3 | Typescript training materials 4 | 5 | Include practices for building common applications 6 | 7 | ## Development 8 | 9 | Shell scripts: 10 | 11 | - `yarn clean` - clean all `*.js` 12 | 13 | - `docker-compose up` - see local preview of GithubPages running locally 14 | 15 | - `yarn markdown` - generate `./src` typescript code into `./docs/pages` `md` documentation. 16 | Includes generation of generated pages index into `docs/pages/index.md`. 17 | 18 | Code in `src/` prefixed with `// @playground-link` will have link to typescript playground 19 | containing code where this comment was mentioned 20 | -------------------------------------------------------------------------------- /converter/convert.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Glob } from "glob"; 3 | import { dirname } from "path"; 4 | import { ensureDir, writeFile } from "fs-extra"; 5 | import * as lz from "lz-string"; 6 | 7 | const source = `${process.cwd()}/src`; 8 | const destination = `${process.cwd()}/docs/pages`; 9 | 10 | console.log("source", source); 11 | 12 | type CodeKind = "code" | "linked-code"; 13 | 14 | interface ResultBlock { 15 | lines: string[]; 16 | type: "comment" | CodeKind; 17 | } 18 | 19 | const parseFile = (sources: string): ResultBlock[] => { 20 | const lines = sources.split("\n"); 21 | // console.log("lines", lines); 22 | const resultingBlocks: ResultBlock[] = []; 23 | let commentBuffer: string[] = []; 24 | let codeBuffer: string[] = []; 25 | let isCode = true; 26 | let codeKind: CodeKind = "code"; 27 | const dumpCode = () => { 28 | if (codeBuffer.length > 0 && codeBuffer.some((x) => x.trim() !== "")) { 29 | resultingBlocks.push({ 30 | type: codeKind, 31 | lines: codeBuffer, 32 | }); 33 | codeBuffer = []; 34 | codeKind = "code"; 35 | } 36 | }; 37 | for (const line of lines) { 38 | const trimmed = line.trim(); 39 | const turnOnCodeLink = trimmed.startsWith("// @playground-link"); 40 | const startComment = trimmed.startsWith("/*"); 41 | const endComment = trimmed.endsWith("*/"); 42 | const oneLineComment = line.startsWith("//"); // check without trimming ! 43 | // console.log( 44 | // "=> ", 45 | // isCode, 46 | // trimmed, 47 | // startComment, 48 | // endComment, 49 | // oneLineComment, 50 | // commentBuffer, 51 | // codeBuffer 52 | // ); 53 | 54 | if (turnOnCodeLink) { 55 | codeKind = "linked-code"; 56 | } 57 | // single line comment 58 | else if ((startComment && endComment) || oneLineComment) { 59 | dumpCode(); 60 | resultingBlocks.push({ 61 | type: "comment", 62 | lines: [line], 63 | }); 64 | } else if (startComment) { 65 | dumpCode(); 66 | commentBuffer.push(trimmed); 67 | isCode = false; 68 | } else if (!isCode && !endComment) { 69 | commentBuffer.push(trimmed); 70 | } else if (!isCode && endComment) { 71 | commentBuffer.push(trimmed); 72 | resultingBlocks.push({ 73 | type: "comment", 74 | lines: commentBuffer, 75 | }); 76 | isCode = true; 77 | commentBuffer = []; 78 | } else { 79 | codeBuffer.push(line); 80 | } 81 | } 82 | dumpCode(); 83 | return resultingBlocks; 84 | }; 85 | 86 | const generateCodeLink = (code: string): string => { 87 | const baseUrl = `https://www.typescriptlang.org/play?#code/`; 88 | const linkUrl = baseUrl + lz.compressToEncodedURIComponent(code); 89 | return `[open code in online editor](${linkUrl})\n`; 90 | }; 91 | 92 | const transform = (parsed: ResultBlock[]): string => { 93 | let result: string[] = []; 94 | for (const part of parsed) { 95 | if (part.type === "code") { 96 | result = result.concat(["```ts", ...part.lines, "```"]); 97 | } else if (part.type === "linked-code") { 98 | result = result.concat([ 99 | "```ts", 100 | ...part.lines, 101 | "```", 102 | generateCodeLink(part.lines.join("\n")), 103 | ]); 104 | } else { 105 | result = result.concat( 106 | part.lines.map( 107 | (x) => 108 | x 109 | .replace(/^\/\*(\*)?/, "") // comment start 110 | .replace(/\*\/$/, "") // comment end 111 | .replace(/^\/\//, "") // one-line comment 112 | .replace(/^\*/, "") // comment body * 113 | ) 114 | ); 115 | } 116 | } 117 | return result.join("\n"); 118 | }; 119 | 120 | const getFiles = async (cwd: string): Promise => 121 | new Promise((res, rej) => { 122 | new Glob(`**/*.ts`, { cwd }, (er, data) => { 123 | if (er) { 124 | return rej(er.toString()); 125 | } 126 | return res(data); 127 | }); 128 | }); 129 | 130 | const generateIndexPage = async (files: string[]) => { 131 | const indexFileName = `${destination}/index.md`; 132 | await ensureDir(destination); 133 | const content = ["# Pages\n"].concat( 134 | files.map((x) => `- [${x.replace(/-/g, " ")}](./${x})\n`) 135 | ); 136 | await writeFile(indexFileName, content.join("\n"), "utf-8"); 137 | }; 138 | 139 | async function main() { 140 | console.log("destination", destination); 141 | await ensureDir(destination); 142 | 143 | const files = await getFiles(source); 144 | const resultingFilenames = []; 145 | console.log(JSON.stringify(files, undefined, 4)); 146 | for (const name of files) { 147 | console.log("fileName", name); 148 | const fileName = `${source}/${name}`; 149 | const distFilename = name.split(".").slice(0, -1).join(".") + ".md"; 150 | const destFileName = `${destination}/${distFilename}`; 151 | const sources = fs.readFileSync(fileName).toString(); 152 | const parsed = parseFile(sources); 153 | console.log("parsed", parsed); 154 | const md = transform(parsed); 155 | console.log("transform\n", md); 156 | await ensureDir(dirname(destFileName)); 157 | await writeFile(destFileName, md); 158 | resultingFilenames.push(distFilename); 159 | } 160 | await generateIndexPage(resultingFilenames); 161 | } 162 | main(); 163 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | services: 4 | jekyll: 5 | environment: 6 | - PAGES_REPO_NWO=omakoleg/typescript-practices 7 | image: jekyll/jekyll:latest 8 | command: jekyll serve --watch --force_polling --verbose 9 | ports: 10 | - 4000:4000 11 | volumes: 12 | - ./docs:/srv/jekyll 13 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | vendor 6 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # gem "jekyll", "~> 4.1.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima" 13 | gem "webrick" 14 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 15 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 16 | gem "github-pages", group: :jekyll_plugins 17 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.1.2) 5 | base64 6 | bigdecimal 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | connection_pool (>= 2.2.5) 9 | drb 10 | i18n (>= 1.6, < 2) 11 | minitest (>= 5.1) 12 | mutex_m 13 | tzinfo (~> 2.0) 14 | addressable (2.8.6) 15 | public_suffix (>= 2.0.2, < 6.0) 16 | base64 (0.2.0) 17 | bigdecimal (3.1.5) 18 | coffee-script (2.4.1) 19 | coffee-script-source 20 | execjs 21 | coffee-script-source (1.11.1) 22 | colorator (1.1.0) 23 | commonmarker (0.23.10) 24 | concurrent-ruby (1.2.2) 25 | connection_pool (2.4.1) 26 | dnsruby (1.70.0) 27 | simpleidn (~> 0.2.1) 28 | drb (2.2.0) 29 | ruby2_keywords 30 | em-websocket (0.5.3) 31 | eventmachine (>= 0.12.9) 32 | http_parser.rb (~> 0) 33 | ethon (0.16.0) 34 | ffi (>= 1.15.0) 35 | eventmachine (1.2.7) 36 | execjs (2.9.1) 37 | faraday (2.9.0) 38 | faraday-net_http (>= 2.0, < 3.2) 39 | faraday-net_http (3.1.0) 40 | net-http 41 | ffi (1.16.3) 42 | forwardable-extended (2.6.0) 43 | gemoji (3.0.1) 44 | github-pages (228) 45 | github-pages-health-check (= 1.17.9) 46 | jekyll (= 3.9.3) 47 | jekyll-avatar (= 0.7.0) 48 | jekyll-coffeescript (= 1.1.1) 49 | jekyll-commonmark-ghpages (= 0.4.0) 50 | jekyll-default-layout (= 0.1.4) 51 | jekyll-feed (= 0.15.1) 52 | jekyll-gist (= 1.5.0) 53 | jekyll-github-metadata (= 2.13.0) 54 | jekyll-include-cache (= 0.2.1) 55 | jekyll-mentions (= 1.6.0) 56 | jekyll-optional-front-matter (= 0.3.2) 57 | jekyll-paginate (= 1.1.0) 58 | jekyll-readme-index (= 0.3.0) 59 | jekyll-redirect-from (= 0.16.0) 60 | jekyll-relative-links (= 0.6.1) 61 | jekyll-remote-theme (= 0.4.3) 62 | jekyll-sass-converter (= 1.5.2) 63 | jekyll-seo-tag (= 2.8.0) 64 | jekyll-sitemap (= 1.4.0) 65 | jekyll-swiss (= 1.0.0) 66 | jekyll-theme-architect (= 0.2.0) 67 | jekyll-theme-cayman (= 0.2.0) 68 | jekyll-theme-dinky (= 0.2.0) 69 | jekyll-theme-hacker (= 0.2.0) 70 | jekyll-theme-leap-day (= 0.2.0) 71 | jekyll-theme-merlot (= 0.2.0) 72 | jekyll-theme-midnight (= 0.2.0) 73 | jekyll-theme-minimal (= 0.2.0) 74 | jekyll-theme-modernist (= 0.2.0) 75 | jekyll-theme-primer (= 0.6.0) 76 | jekyll-theme-slate (= 0.2.0) 77 | jekyll-theme-tactile (= 0.2.0) 78 | jekyll-theme-time-machine (= 0.2.0) 79 | jekyll-titles-from-headings (= 0.5.3) 80 | jemoji (= 0.12.0) 81 | kramdown (= 2.3.2) 82 | kramdown-parser-gfm (= 1.1.0) 83 | liquid (= 4.0.4) 84 | mercenary (~> 0.3) 85 | minima (= 2.5.1) 86 | nokogiri (>= 1.13.6, < 2.0) 87 | rouge (= 3.26.0) 88 | terminal-table (~> 1.4) 89 | github-pages-health-check (1.17.9) 90 | addressable (~> 2.3) 91 | dnsruby (~> 1.60) 92 | octokit (~> 4.0) 93 | public_suffix (>= 3.0, < 5.0) 94 | typhoeus (~> 1.3) 95 | html-pipeline (2.14.3) 96 | activesupport (>= 2) 97 | nokogiri (>= 1.4) 98 | http_parser.rb (0.8.0) 99 | i18n (1.14.1) 100 | concurrent-ruby (~> 1.0) 101 | jekyll (3.9.3) 102 | addressable (~> 2.4) 103 | colorator (~> 1.0) 104 | em-websocket (~> 0.5) 105 | i18n (>= 0.7, < 2) 106 | jekyll-sass-converter (~> 1.0) 107 | jekyll-watch (~> 2.0) 108 | kramdown (>= 1.17, < 3) 109 | liquid (~> 4.0) 110 | mercenary (~> 0.3.3) 111 | pathutil (~> 0.9) 112 | rouge (>= 1.7, < 4) 113 | safe_yaml (~> 1.0) 114 | jekyll-avatar (0.7.0) 115 | jekyll (>= 3.0, < 5.0) 116 | jekyll-coffeescript (1.1.1) 117 | coffee-script (~> 2.2) 118 | coffee-script-source (~> 1.11.1) 119 | jekyll-commonmark (1.4.0) 120 | commonmarker (~> 0.22) 121 | jekyll-commonmark-ghpages (0.4.0) 122 | commonmarker (~> 0.23.7) 123 | jekyll (~> 3.9.0) 124 | jekyll-commonmark (~> 1.4.0) 125 | rouge (>= 2.0, < 5.0) 126 | jekyll-default-layout (0.1.4) 127 | jekyll (~> 3.0) 128 | jekyll-feed (0.15.1) 129 | jekyll (>= 3.7, < 5.0) 130 | jekyll-gist (1.5.0) 131 | octokit (~> 4.2) 132 | jekyll-github-metadata (2.13.0) 133 | jekyll (>= 3.4, < 5.0) 134 | octokit (~> 4.0, != 4.4.0) 135 | jekyll-include-cache (0.2.1) 136 | jekyll (>= 3.7, < 5.0) 137 | jekyll-mentions (1.6.0) 138 | html-pipeline (~> 2.3) 139 | jekyll (>= 3.7, < 5.0) 140 | jekyll-optional-front-matter (0.3.2) 141 | jekyll (>= 3.0, < 5.0) 142 | jekyll-paginate (1.1.0) 143 | jekyll-readme-index (0.3.0) 144 | jekyll (>= 3.0, < 5.0) 145 | jekyll-redirect-from (0.16.0) 146 | jekyll (>= 3.3, < 5.0) 147 | jekyll-relative-links (0.6.1) 148 | jekyll (>= 3.3, < 5.0) 149 | jekyll-remote-theme (0.4.3) 150 | addressable (~> 2.0) 151 | jekyll (>= 3.5, < 5.0) 152 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 153 | rubyzip (>= 1.3.0, < 3.0) 154 | jekyll-sass-converter (1.5.2) 155 | sass (~> 3.4) 156 | jekyll-seo-tag (2.8.0) 157 | jekyll (>= 3.8, < 5.0) 158 | jekyll-sitemap (1.4.0) 159 | jekyll (>= 3.7, < 5.0) 160 | jekyll-swiss (1.0.0) 161 | jekyll-theme-architect (0.2.0) 162 | jekyll (> 3.5, < 5.0) 163 | jekyll-seo-tag (~> 2.0) 164 | jekyll-theme-cayman (0.2.0) 165 | jekyll (> 3.5, < 5.0) 166 | jekyll-seo-tag (~> 2.0) 167 | jekyll-theme-dinky (0.2.0) 168 | jekyll (> 3.5, < 5.0) 169 | jekyll-seo-tag (~> 2.0) 170 | jekyll-theme-hacker (0.2.0) 171 | jekyll (> 3.5, < 5.0) 172 | jekyll-seo-tag (~> 2.0) 173 | jekyll-theme-leap-day (0.2.0) 174 | jekyll (> 3.5, < 5.0) 175 | jekyll-seo-tag (~> 2.0) 176 | jekyll-theme-merlot (0.2.0) 177 | jekyll (> 3.5, < 5.0) 178 | jekyll-seo-tag (~> 2.0) 179 | jekyll-theme-midnight (0.2.0) 180 | jekyll (> 3.5, < 5.0) 181 | jekyll-seo-tag (~> 2.0) 182 | jekyll-theme-minimal (0.2.0) 183 | jekyll (> 3.5, < 5.0) 184 | jekyll-seo-tag (~> 2.0) 185 | jekyll-theme-modernist (0.2.0) 186 | jekyll (> 3.5, < 5.0) 187 | jekyll-seo-tag (~> 2.0) 188 | jekyll-theme-primer (0.6.0) 189 | jekyll (> 3.5, < 5.0) 190 | jekyll-github-metadata (~> 2.9) 191 | jekyll-seo-tag (~> 2.0) 192 | jekyll-theme-slate (0.2.0) 193 | jekyll (> 3.5, < 5.0) 194 | jekyll-seo-tag (~> 2.0) 195 | jekyll-theme-tactile (0.2.0) 196 | jekyll (> 3.5, < 5.0) 197 | jekyll-seo-tag (~> 2.0) 198 | jekyll-theme-time-machine (0.2.0) 199 | jekyll (> 3.5, < 5.0) 200 | jekyll-seo-tag (~> 2.0) 201 | jekyll-titles-from-headings (0.5.3) 202 | jekyll (>= 3.3, < 5.0) 203 | jekyll-watch (2.2.1) 204 | listen (~> 3.0) 205 | jemoji (0.12.0) 206 | gemoji (~> 3.0) 207 | html-pipeline (~> 2.2) 208 | jekyll (>= 3.0, < 5.0) 209 | kramdown (2.3.2) 210 | rexml 211 | kramdown-parser-gfm (1.1.0) 212 | kramdown (~> 2.0) 213 | liquid (4.0.4) 214 | listen (3.8.0) 215 | rb-fsevent (~> 0.10, >= 0.10.3) 216 | rb-inotify (~> 0.9, >= 0.9.10) 217 | mercenary (0.3.6) 218 | minima (2.5.1) 219 | jekyll (>= 3.5, < 5.0) 220 | jekyll-feed (~> 0.9) 221 | jekyll-seo-tag (~> 2.1) 222 | minitest (5.20.0) 223 | mutex_m (0.2.0) 224 | net-http (0.4.1) 225 | uri 226 | nokogiri (1.16.0-x86_64-linux) 227 | racc (~> 1.4) 228 | octokit (4.25.1) 229 | faraday (>= 1, < 3) 230 | sawyer (~> 0.9) 231 | pathutil (0.16.2) 232 | forwardable-extended (~> 2.6) 233 | public_suffix (4.0.7) 234 | racc (1.7.3) 235 | rb-fsevent (0.11.2) 236 | rb-inotify (0.10.1) 237 | ffi (~> 1.0) 238 | rexml (3.2.6) 239 | rouge (3.26.0) 240 | ruby2_keywords (0.0.5) 241 | rubyzip (2.3.2) 242 | safe_yaml (1.0.5) 243 | sass (3.7.4) 244 | sass-listen (~> 4.0.0) 245 | sass-listen (4.0.0) 246 | rb-fsevent (~> 0.9, >= 0.9.4) 247 | rb-inotify (~> 0.9, >= 0.9.7) 248 | sawyer (0.9.2) 249 | addressable (>= 2.3.5) 250 | faraday (>= 0.17.3, < 3) 251 | simpleidn (0.2.1) 252 | unf (~> 0.1.4) 253 | terminal-table (1.8.0) 254 | unicode-display_width (~> 1.1, >= 1.1.1) 255 | typhoeus (1.4.1) 256 | ethon (>= 0.9.0) 257 | tzinfo (2.0.6) 258 | concurrent-ruby (~> 1.0) 259 | unf (0.1.4) 260 | unf_ext 261 | unf_ext (0.0.9.1) 262 | unicode-display_width (1.8.0) 263 | uri (0.13.0) 264 | webrick (1.8.1) 265 | 266 | PLATFORMS 267 | x86_64-linux-musl 268 | 269 | DEPENDENCIES 270 | github-pages 271 | minima 272 | webrick 273 | 274 | BUNDLED WITH 275 | 2.3.25 276 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: Typescript Practices 22 | email: oleg.makiienko@gmail.com 23 | description: >- # this means to ignore newlines until "baseurl:" 24 | Typescript Practices 25 | # baseurl: "" # the subpath of your site, e.g. /blog 26 | # url: "" # the base hostname & protocol for your site, e.g. http://example.com 27 | twitter_username: omakoleg 28 | github_username: omakoleg 29 | google_analytics: G-9G9BP63PJ1 30 | 31 | # Build settings 32 | theme: jekyll-theme-cayman 33 | # plugins: 34 | # - jekyll-feed 35 | # Exclude from processing. 36 | # The following items will not be processed, by default. 37 | # Any item listed under the `exclude:` key here will be automatically added to 38 | # the internal "default list". 39 | # 40 | # Excluded items can be processed by explicitly listing the directories or 41 | # their entries' file path in the `include:` list. 42 | # 43 | # exclude: 44 | # - .sass-cache/ 45 | # - .jekyll-cache/ 46 | # - gemfiles/ 47 | # - Gemfile 48 | # - Gemfile.lock 49 | # - node_modules/ 50 | # - vendor/bundle/ 51 | # - vendor/cache/ 52 | # - vendor/gems/ 53 | # - vendor/ruby/ 54 | -------------------------------------------------------------------------------- /docs/_includes/google-analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | 6 | div.language-ts + p a { 7 | float: right; 8 | margin-top: -20px !important; 9 | font-size: 0.8em; 10 | background-color: #f3f6fa; 11 | border: solid 1px #dce6f0; 12 | border-top: 0px; 13 | padding: 2px 10px; 14 | color: darkgrey; 15 | } 16 | 17 | @media screen and (min-width: 64em) { 18 | .main-content { 19 | max-width: 80rem; 20 | padding: 2rem 6rem; 21 | margin: 0 auto; 22 | font-size: 1.1rem; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/content/jest.md: -------------------------------------------------------------------------------- 1 | # Jest 2 | 3 | Jest is a delightful JavaScript Testing Framework with a focus on simplicity 4 | 5 | ```sh 6 | yarn add --dev jest @types/jest ts-jest 7 | ``` 8 | 9 | Installs jest, types and wiring with typescript 10 | 11 | Sample: 12 | 13 | ```ts 14 | export const sum = (a: number, b: number): number => a + b; 15 | // 16 | import { sum } from "./sum"; 17 | // `test` 18 | test("adds 1 + 2 to equal 3", () => { 19 | expect(sum(1, 2)).toBe(3); 20 | }); 21 | // `it` 22 | it("adds 1 + 2 to equal 3", () => { 23 | expect(sum(1, 2)).toBe(3); 24 | }); 25 | ``` 26 | 27 | Using `describe` to group tests 28 | 29 | ```ts 30 | describe(".asyncSum", () => { 31 | describe("success cases", () => { 32 | it("works with dummy payload", () => {}); 33 | it("works with broken data", () => {}); 34 | }); 35 | }); 36 | ``` 37 | 38 | Using `async` in tests 39 | 40 | ```ts 41 | export const asyncSum = (a: number, b: number): Promise => 42 | Promise.resolve(a + b); 43 | 44 | it("works with async", async () => { 45 | const result = await asyncSum(1, 2); 46 | expect(result).toBe(3); 47 | }); 48 | ``` 49 | 50 | _no need to put `async` in `describe` sections_ 51 | 52 | Using `callbacks` in tests 53 | 54 | ```ts 55 | test("the data is peanut butter", (done) => { 56 | function callback(data) { 57 | try { 58 | expect(data).toBe("peanut butter"); 59 | done(); 60 | } catch (error) { 61 | done(error); 62 | } 63 | } 64 | fetchData(callback); 65 | }); 66 | ``` 67 | 68 | Using `Promise` in tests 69 | 70 | > Better to use `async` approach and `await` for promise result and check it outside ot `.then` 71 | 72 | ```ts 73 | test("the data is peanut butter", () => { 74 | return fetchData().then((data) => { 75 | expect(data).toBe("peanut butter"); 76 | }); 77 | }); 78 | ``` 79 | 80 | Running only some tests out of all in a file: 81 | 82 | `my-test.test.ts` 83 | 84 | ```ts 85 | describe(".asyncSum", () => { 86 | it("works with dummy payload", () => {}); 87 | it("works with broken data", () => {}); 88 | it.only("need to verify this", () => {}); 89 | it.only("and this", () => {}); 90 | it("skipped as well", () => {}); 91 | }); 92 | ``` 93 | 94 | Execute only one file tests. And only `.only` tests will be running, all others will be skipped 95 | 96 | ```sh 97 | jest -i my-test.test.ts 98 | ``` 99 | 100 | Using generated tests 101 | 102 | ```ts 103 | test.each([ 104 | [1, 1, 2], 105 | [1, 2, 3], 106 | [2, 1, 3], 107 | ])(".add(%i, %i)", (a, b, expected) => { 108 | expect(a + b).toBe(expected); 109 | }); 110 | ``` 111 | 112 | Before and After 113 | 114 | ```ts 115 | beforeAll(() => {}); 116 | afterAll(() => {}); 117 | describe("scoped setups", () => { 118 | beforeAll(() => {}); 119 | afterAll(() => {}); 120 | }); 121 | // per test 122 | beforeEach(() => {}); 123 | afterEach(() => {}); 124 | ``` 125 | 126 | Using `async` is fine also: 127 | 128 | ```ts 129 | afterAll(async () => { 130 | await cleanupDatabase(); 131 | console.log("Database is removed"); 132 | }); 133 | ``` 134 | 135 | ## configuration 136 | 137 | Config file name `jest.config.js`, or any other passed in cli with `--config`. 138 | 139 | Configuration can also be provided in `package.json` 140 | 141 | ```json 142 | { 143 | "jest": { 144 | "preset": "...", 145 | "globals": { 146 | "ts-jest": { 147 | "tsConfig": "tsconfig.json" 148 | } 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | There are many possible configurations 155 | 156 | ## expectations 157 | 158 | There are many ways to match result with expected value 159 | 160 | Most commonly used 161 | 162 | ```ts 163 | test("dummy test", () => { 164 | // Comparing sample values like number, boolean, string 165 | expect(1).toBe(1); 166 | // `not` cases available in any expectation 167 | expect(1).not.toBe(2); 168 | // Comparing array length 169 | expect([1, 2, 3]).toHaveLength(3); 170 | // Something is defined 171 | const value: number | undefined = 1; 172 | expect(value).toBeDefined(); 173 | // opposite to defined 174 | expect(value).not.toBeUndefined(); 175 | // Deep comparing 176 | const object = { 177 | a: 10, 178 | b: [1, 2, 3], 179 | }; 180 | expect(object).toEqual(object); 181 | // regex 182 | expect("car").toMatch(/.*/); 183 | // throws 184 | expect(() => { 185 | throw new Error(`Bam!`); 186 | }).toThrow(); 187 | }); 188 | ``` 189 | 190 | ## mocks 191 | 192 | It is useful to mock modules and provide dummy expected implementations without external network communications 193 | 194 | Topic itself is complex and I will recommend to read docs: 195 | 196 | - mock functions 197 | - mock classes 198 | - mock with separate module files 199 | 200 | `jest.fn` function mock 201 | 202 | ```ts 203 | const mockCallback = jest.fn((x) => 42 + x); 204 | forEach([0, 1], mockCallback); 205 | 206 | // The mock function is called twice 207 | expect(mockCallback.mock.calls.length).toBe(2); 208 | 209 | // The first argument of the first call to the function was 0 210 | expect(mockCallback.mock.calls[0][0]).toBe(0); 211 | ``` 212 | 213 | Mock returned values 214 | 215 | ```ts 216 | const myMock = jest.fn(); 217 | 218 | myMock.mockReturnValueOnce(10).mockReturnValueOnce("x").mockReturnValue(true); 219 | 220 | console.log(myMock(), myMock(), myMock(), myMock()); 221 | ``` 222 | 223 | Mocking module fully 224 | 225 | ```ts 226 | import * as uuid from "uuid"; 227 | 228 | jest.mock("uuid", () => ({ 229 | v4: () => "some-uuid", 230 | })); 231 | 232 | uuid.v4(); // => some-uuid 233 | ``` 234 | 235 | Capture some values by mock function 236 | 237 | ```ts 238 | import { sendDistributionMetric } from "datadog-lambda-js"; 239 | const metrics: any[] = []; 240 | 241 | jest.mock("datadog-lambda-js", () => ({ 242 | sendDistributionMetric: (...args: any) => { 243 | metrics.push(args); 244 | }, 245 | })); 246 | sendDistributionMetric("my-metric", 1); 247 | 248 | expect(metrics).toEqual([["my-metric", 1]]); 249 | ``` 250 | 251 | Checking mocks been called 252 | 253 | ```ts 254 | import { trackRecordPushed, trackRecordRejected } from "./metrics"; 255 | 256 | jest.mock("./metrics", () => ({ 257 | trackRecordPushed: jest.fn(), 258 | trackRecordRejected: jest.fn(), 259 | })); 260 | 261 | const trackRecordPushedMocked = trackRecordPushed as jest.Mock; 262 | const trackRecordRejectedMock = trackRecordRejected as jest.Mock; 263 | // Call some code internally including "./metrics" and calling `trackRecordPushed` 264 | // Verify: 265 | expect(trackRecordPushed).toHaveBeenCalledWith(3); // check arguments also 266 | ``` 267 | 268 | ## jest-dynamodb 269 | 270 | Jest has excellent plugin to setup test `DynamoDB` within tests 271 | 272 | 273 | 274 | ```sh 275 | yarn add @shelf/jest-dynamodb --dev 276 | ``` 277 | 278 | Use as `preset` (`package.json/jest`) 279 | 280 | ```json 281 | { 282 | "preset": "@shelf/jest-dynamodb" 283 | } 284 | ``` 285 | 286 | Or provision manually (`package.json/jest`) 287 | 288 | ```json 289 | { 290 | "globalSetup": "@shelf/jest-dynamodb/setup.js", 291 | "globalTeardown": "@shelf/jest-dynamodb/teardown.js", 292 | "testEnvironment": "@shelf/jest-dynamodb/environment.js" 293 | } 294 | ``` 295 | 296 | Describe databases in `jest-dynamodb-config.js`: 297 | 298 | ```js 299 | module.exports = { 300 | tables: [ 301 | { 302 | TableName: `files`, 303 | KeySchema: [{ AttributeName: "id", KeyType: "HASH" }], 304 | AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }], 305 | ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 }, 306 | }, 307 | ], 308 | }; 309 | ``` 310 | 311 | In tests create client connected to test database: 312 | 313 | ```ts 314 | import { DynamoDB } from "aws-sdk"; 315 | const config = { 316 | convertEmptyValues: true, 317 | endpoint: "localhost:8000", 318 | sslEnabled: false, 319 | region: "local-env", 320 | }; 321 | 322 | const docClient = new DynamoDB.DocumentClient(config); 323 | ``` 324 | -------------------------------------------------------------------------------- /docs/content/lambda.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda 2 | 3 | Lambda is a small handler supposed to do one thing. 4 | It is crucial to make logic simple and optimized. This will reduce runtime costs and code maintenance. 5 | 6 | There are no fixed requirements how code should looks like, but there are some practical 7 | solutions proven to be useful. 8 | 9 | ## @types/aws-lambda 10 | 11 | `aws-sdk` package provide everything needed to work with any AWS service. 12 | 13 | But it doesn't have types for event shapes received in lambda handler function 14 | from various sources. 15 | 16 | ```ts 17 | import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; 18 | 19 | export const handleApiRequest = async ( 20 | event: APIGatewayProxyEvent 21 | ): Promise => { 22 | await Promise.resolve(1); 23 | return { 24 | statusCode: 200, 25 | body: "ok", 26 | }; 27 | }; 28 | ``` 29 | 30 | In day-to-day practice mostly used are: 31 | 32 | - `APIGatewayProxyEvent` and `APIGatewayProxyResult` for API Gateway request/response 33 | - `DynamoDBStreamEvent` for lambdas attached to DynamoDB stream. 34 | - `KinesisStreamEvent`for lambdas attached to Kinesis stream. 35 | - `SNSEvent` triggered by EventSourceMapping attached to SNS 36 | - `SQSEvent` triggered by EventSourceMapping attached to SQS 37 | 38 | Package also provide full Lambda handler function types 39 | 40 | ```ts 41 | import { DynamoDBStreamHandler, DynamoDBStreamEvent } from "aws-lambda"; 42 | 43 | export const handleStreamEvent: DynamoDBStreamHandler = async ( 44 | event: DynamoDBStreamEvent 45 | ): Promise => { 46 | await Promise.resolve(1); 47 | console.log("Process db event"); 48 | }; 49 | ``` 50 | 51 | Internally handler type has definition: 52 | 53 | ```ts 54 | export type APIGatewayProxyHandler = Handler< 55 | APIGatewayProxyEvent, 56 | APIGatewayProxyResult 57 | >; 58 | export type DynamoDBStreamHandler = Handler; 59 | // Handler Generic 60 | export type Handler = ( 61 | event: TEvent, 62 | context: Context, 63 | callback: Callback 64 | ) => void | Promise; 65 | // For callback use case 66 | export type Callback = ( 67 | error?: Error | string | null, 68 | result?: TResult 69 | ) => void; 70 | ``` 71 | 72 | _Such types available for all types of handlers_ 73 | 74 | ## Environment 75 | 76 | Environment variables in Lambda function available in any file by using `process.env.VARIABLE_NAME` 77 | 78 | Nevertheless is recommended to have one environment constant and pass it as dependency: 79 | 80 | `index.ts` 81 | 82 | ```ts 83 | const config: EnvConfig = { 84 | databaseName: process.env.DATABASE_NAME!, 85 | gsiCustomerIdName: process.env.CUSTOMER_ID_GSI_NAME!, 86 | }; 87 | // use config 88 | ``` 89 | 90 | This will isolate all external setup and will be useful for testing lambda code in future 91 | 92 | ## Caching 93 | 94 | When lambda called there is initialization delay happening to create runtime environment 95 | for a given handler. 96 | This time depends on multiple factors and not fixed. 97 | Based on X-Ray traces DynamoDB event handler could spend up to 2 second to start function execution. 98 | 99 | There is "caching" trick often used to reduce this delay: 100 | 101 | > Initialize all aws-sdk and other classes outside of lambda handler function 102 | 103 | ```ts 104 | import { DynamoDBStreamEvent, SQSEvent } from "aws-lambda"; 105 | import AWS, { DynamoDB } from "aws-sdk"; 106 | import { initCustomerValidator } from "./validator"; 107 | const region = "eu-west-1"; 108 | // Create DocumentClient and cache it. 109 | const docClient = new DynamoDB.DocumentClient({ 110 | region, 111 | }); 112 | // Create validator and cache it. 113 | const customerValidator = initCustomerValidator(); 114 | // Resolve ENV 115 | const config: EnvConfig = { 116 | databaseName: process.env.DATABASE_NAME!, 117 | gsiCustomerIdName: process.env.CUSTOMER_ID_GSI_NAME!, 118 | }; 119 | export async function eventHandler(event: DynamoDBStreamEvent): Promise { 120 | const eventNames = event.Records.map((x) => x.eventName); 121 | console.log(`Received DynamoDB Stream events: ${eventNames}`); 122 | console.log(`Writing data to ${config.databaseName} by ${gsiCustomerIdName}`); 123 | } 124 | ``` 125 | 126 | Next time Lambda called to process stream event there are high chances 127 | that Lambda runtime agent will still be available and everything created outside of `eventHandler` 128 | will be still available: `docClient`, `customerValidator` and env object. 129 | 130 | Caches like everywhere could be there, or not, nobody knows. If your lambda 131 | was not triggered for some time there are high chances that all "cached" objects 132 | have to be initialized again. 133 | But if they are still there and agent is alive, then each Lambda call will cost less. 134 | 135 | Recently was added possibility to always keep pool of already initialized agents 136 | with Lambdas in "warmup state". 137 | This of course has additional costs but allows to keep at least one Lambda which caches will 138 | not be removed and completely remove initialization time. 139 | 140 | Read more about Lambda [provisioned concurrency](https://docs.aws.amazon.com/lambda/latest/dg/invocation-scaling.html) 141 | 142 | ## DI container 143 | 144 | Lambda code could became pretty messy and some time. 145 | 146 | It is better to use Dependency Injection approach from early stages. 147 | All external communications are wrapped into types which can be replaced 148 | later in tests 149 | 150 | > Sample of Lambda stream event handler which resolves parameters form `SSM`, 151 | > skips any event except 'INSERT' 152 | > validates something in payload 153 | > and pushes data into another `DynamoDB` table 154 | 155 | Assumed here that `DynamoDBStreamEvent` will be triggered for one record 156 | 157 | `index.ts` Lambda event handler 158 | 159 | ```ts 160 | import { DynamoDBStreamEvent, DynamoDBStreamHandler } from "aws-lambda"; 161 | import { DynamoDB, SSM } from "aws-sdk"; 162 | import { initValidator } from "./validator"; // definition skipped 163 | import { eventHandlerInternal } from "./lib"; 164 | 165 | const region = "eu-west-1"; 166 | // Create cached clients 167 | const docClient = new DynamoDB.DocumentClient({ 168 | region, 169 | }); 170 | const ssm = new SSM({ 171 | region, 172 | }); 173 | // Create validator and cache it. 174 | const validator = initValidator(); 175 | // Resolve ENV 176 | const config: EnvConfig = { 177 | ssmConfigKey: process.env.SSM_CONFIG_KEY!, 178 | }; 179 | // Lambda handler 180 | export const eventHandler = eventHandlerInternal( 181 | docClient, 182 | ssm, 183 | config, 184 | validator 185 | ); 186 | ``` 187 | 188 | `lib.ts` With implementation 189 | 190 | ```ts 191 | export const eventHandlerInternal = 192 | ( 193 | docClient: DynamoDB.DocumentClient, 194 | ssm: SSM, 195 | config: EnvConfig, 196 | validator: Validator 197 | ): DynamoDBStreamHandler => 198 | async (event: DynamoDBStreamEvent): Promise => { 199 | const { Parameter } = await ssm 200 | .getParameter({ 201 | Name: config.ssmConfigKey, 202 | }) 203 | .promise(); 204 | const tableName = Parameter!.Value!; 205 | const record = event.Records[0]; 206 | if (record.eventName === "INSERT") { 207 | // decode payload 208 | const recordData = DynamoDB.Converter.unmarshall( 209 | record.dynamodb!.NewImage! 210 | ); 211 | const isValid = await validator.validate(recordData); 212 | if (isValid) { 213 | await docClient 214 | .put({ 215 | TableName: tableName, 216 | Item: recordData, 217 | }) 218 | .promise(); 219 | } 220 | } else { 221 | console.log(`Skipped ${JSON.stringify(record)}`); 222 | } 223 | }; 224 | ``` 225 | 226 | Here `eventHandlerInternal` is a curried function which returns Lambda event handler based 227 | on `docClient`,`ssm` and `config`. 228 | 229 | Handler itself in of type `DynamoDBStreamHandler` which has one parameter: `event: DynamoDBStreamEvent` 230 | 231 | Now it is easy to write tests for `eventHandlerInternal` and mock any external client. 232 | 233 | ## Replacing aws-sdk in tests 234 | 235 | In `jest` tests provided earlier code with `DynamoDB.DocumentClient` as parameter could be 236 | replaced by "fake" implementation. 237 | 238 | This will reduce tests running time and don't have any side effects on data in shared tests database. 239 | 240 | `mocked-dynamodb.ts` provides required function `put`. This "fake" function is capturing 241 | function cll parameters and provide already expected output. 242 | 243 | ```ts 244 | import { DynamoDB } from "aws-sdk"; 245 | 246 | interface Props { 247 | putOutput?: DynamoDB.DocumentClient.PutItemOutput; 248 | batchWriteOutput?: DynamoDB.DocumentClient.BatchWriteItemOutput; 249 | } 250 | 251 | interface MockedDynamoDb { 252 | docClient: DynamoDB.DocumentClient; 253 | putParams: DynamoDB.DocumentClient.PutItemInput[]; 254 | batchWriteParams: DynamoDB.DocumentClient.BatchWriteItemInput[]; 255 | } 256 | 257 | export const mockedDynamoDb = ({ 258 | putOutput, 259 | batchWriteOutput, 260 | }: Props): MockedDynamoDb => { 261 | const putParams: DynamoDB.DocumentClient.PutItemInput[] = []; 262 | const batchWriteParams: DynamoDB.DocumentClient.BatchWriteItemInput[] = []; 263 | return { 264 | docClient: { 265 | batchWrite: (params: DynamoDB.DocumentClient.BatchWriteItemInput) => { 266 | batchWriteParams.push(params); 267 | return { 268 | promise: () => Promise.resolve(batchWriteOutput), 269 | }; 270 | }, 271 | put: (params: DynamoDB.DocumentClient.PutItemInput) => { 272 | putParams.push(params); 273 | return { 274 | promise: () => Promise.resolve(putOutput), 275 | }; 276 | }, 277 | } as unknown as DynamoDB.DocumentClient, 278 | putParams, 279 | batchWriteParams, 280 | }; 281 | }; 282 | ``` 283 | 284 | Same can be done with `SSM`: 285 | 286 | `mocked-ssm.ts`: 287 | 288 | ```ts 289 | import { SSM } from "aws-sdk"; 290 | import { GetParameterRequest, GetParameterResult } from "aws-sdk/clients/ssm"; 291 | 292 | interface Props { 293 | getParameter?: GetParameterResult; 294 | } 295 | interface MockedSSM { 296 | ssm: SSM; 297 | getParameterParams: GetParameterRequest[]; 298 | } 299 | 300 | export const mockedSsm = ({ getParameter }: Props): MockedSSM => { 301 | const getParameterParams: GetParameterRequest[] = []; 302 | return { 303 | ssm: { 304 | getParameter: (p: GetParameterRequest) => { 305 | getParameterParams.push(p); 306 | return { 307 | promise: () => Promise.resolve(getParameter), 308 | }; 309 | }, 310 | } as unknown as SSM, 311 | getParameterParams, 312 | }; 313 | }; 314 | ``` 315 | 316 | In tests later for `eventHandlerInternal`: 317 | 318 | ```ts 319 | export const eventHandlerInternal = ( 320 | docClient: DynamoDB.DocumentClient, 321 | ssm: SSM, 322 | config: EnvConfig, 323 | // validator: Validator 324 | ): DynamoDBStreamHandler => async ( 325 | event: DynamoDBStreamEvent 326 | ): Promise 327 | 328 | describe("Lambda Test", () => { 329 | it("success flow", async () => { 330 | // mock database 331 | const { docClient, putParams } = mockedDynamoDb({}); 332 | // mock parameter result 333 | const { ssm, getParameterParams } = mockedSsm({ 334 | getParameter: { 335 | Parameter: { 336 | Value: "test", 337 | }, 338 | }, 339 | }); 340 | await eventHandlerInternal(docClient, ssm, { 341 | ssmConfigKey: "ssm-key", 342 | })({ 343 | /* some database event*/ 344 | }); 345 | // called only once 346 | expect(putParams).toHaveLength(1); 347 | // what parameters? 348 | expect(putParams).toEqual({ 349 | TableName: "test", 350 | Item: { 351 | /** database event data shape */ 352 | }, 353 | }); 354 | // called once 355 | expect(getParameterParams).toHaveLength(1); 356 | // what parameters? 357 | expect(getParameterParams).toEqual({ 358 | Name: "ssm-key", 359 | }); 360 | }); 361 | }); 362 | ``` 363 | -------------------------------------------------------------------------------- /docs/content/lint-format.md: -------------------------------------------------------------------------------- 1 | # Lint 2 | 3 | > lint, or a linter, is a static code analysis tool used to flag programming errors, bugs, stylistic errors, and suspicious constructs. 4 | 5 | In typescript world most popular is [`eslint`](https://eslint.org/). 6 | 7 | > `tslint` is deprecated and should not be used ! 8 | 9 | [`Rome`](https://github.com/romefrontend/rome) is making some progress recently and could became popular tool in future 10 | 11 | As usually available as NPM package 12 | 13 | ```sh 14 | npm install eslint --save-dev 15 | yarn add eslint -D 16 | ``` 17 | 18 | Generate local configuration: `eslint --init`. It will generate `.eslintrc` file. 19 | 20 | Also configuration can be given in `package.json` 21 | 22 | ```json 23 | { 24 | "eslintConfig": { 25 | "env": { 26 | "node": true 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | Few most commonly used config sections: 33 | 34 | - `plugins` to define external additional plugins performing additional code checks 35 | - `extends` to provide multiple already pre-defined configurations 36 | - `rules` to tune any check in any plugin to make it enabled, disabled or just print warning. 37 | Allows to specify additional config per rule 38 | - `env` to specify what runtime will be used when performing checks, `browser, node` 39 | 40 | `eslint` is intended to be used with `javascript` but with additional plugins it will adapt to `typescript`. 41 | 42 | Plugin is [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) 43 | 44 | Few configurations should be provided to be able to use `typescript`: 45 | 46 | ```txt 47 | parser: '@typescript-eslint/parser', 48 | plugins: [ 49 | '@typescript-eslint', 50 | ], 51 | extends: [ 52 | 'eslint:recommended', 53 | 'plugin:@typescript-eslint/recommended', 54 | ], 55 | ``` 56 | 57 | _check more setup [instructions](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md)_ 58 | 59 | After you could check js/ts code for configured issues: 60 | 61 | ```sh 62 | yarn eslint . --ext .js,.ts 63 | ``` 64 | 65 | `eslint` could also apply fixes `--fix` for most issues where it is possible and rules have instructions how to 66 | do that fixes 67 | 68 | ```sh 69 | eslint --ext=ts --fix . 70 | ``` 71 | 72 | Tweaking rules to disallow use of `console.log`: 73 | 74 | ```json 75 | { 76 | "rules": { 77 | // disable console.log 78 | "no-console": "on" 79 | } 80 | } 81 | ``` 82 | 83 | For each rule there will be page explaining it [rules/no-console](https://eslint.org/docs/rules/no-console) 84 | 85 | ## Prettier 86 | 87 | 88 | 89 | - An opinionated code formatter 90 | - Supports many languages 91 | - Integrates with most editors 92 | - Has few options 93 | 94 | This is most of options which could be configured: 95 | 96 | ```json 97 | { 98 | "trailingComma": "es5", 99 | "tabWidth": 4, 100 | "semi": false, 101 | "singleQuote": true 102 | } 103 | ``` 104 | 105 | Configuration file is picked up automatically when named with `.prettierrc` or some other alternatives. 106 | 107 | Prettier supports formatting for multiple formats: 108 | `JavaScript, JSX, TypeScript, CSS, Less, SCSS, HTML ,JSON, GraphQL, Markdown, YAML` 109 | 110 | # Lint and Format in typescript project 111 | 112 | `eslint` and `prettier` could both format codebase so they should be linked together to not override each other. 113 | 114 | Together with `typescript` setup it brings some wiring complexity. 115 | 116 | Some dependencies should be installed 117 | 118 | ```sh 119 | yarn add -D @typescript-eslint/eslint-plugin 120 | yarn add -D @typescript-eslint/parser 121 | yarn add -D eslint 122 | yarn add -D eslint-config-prettier 123 | yarn add -D eslint-plugin-import 124 | yarn add -D eslint-plugin-prettier 125 | yarn add -D prettier 126 | ``` 127 | 128 | `eslint` config should have plugins and extends configured: 129 | 130 | ```js 131 | { 132 | extends: [ 133 | 'eslint:recommended', 134 | 'plugin:@typescript-eslint/eslint-recommended', 135 | 'plugin:@typescript-eslint/recommended', 136 | 'prettier/@typescript-eslint', 137 | 'plugin:prettier/recommended', 138 | ], 139 | parser: '@typescript-eslint/parser', 140 | parserOptions: { 141 | project: './tsconfig.json', 142 | }, 143 | plugins: ['@typescript-eslint', 'prettier'] 144 | } 145 | ``` 146 | 147 | After this some scripts could be added into `package.json` to execute common commands: 148 | 149 | ```json 150 | { 151 | "lint": "eslint --ext=ts .", // check for lint issues 152 | "lint:fix": "yarn lint --fix", // fix lint issues 153 | "format": "prettier --list-different .", // check for formatting issues 154 | "format:fix": "prettier --write .", // fix formatting issues 155 | "fix": "yarn lint:fix && yarn format:fix" // fix everything possible to fix 156 | } 157 | ``` 158 | -------------------------------------------------------------------------------- /docs/content/modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Starting with ECMAScript 2015, JavaScript has a concept of modules. TypeScript shares this concept. 4 | 5 | `file.ts` 6 | 7 | ```ts 8 | export const Test: string = "abc"; 9 | export const Test2: string = "abc"; 10 | export function TestFunction() {} 11 | export const constFunction = () => {}; 12 | export class Dummy {} 13 | export type Test = string; 14 | ``` 15 | 16 | `index.ts` 17 | 18 | ```ts 19 | import { Test, Test2, constFunction, TestFunction, Dummy, Test } from "./file"; 20 | ``` 21 | 22 | Import with namespace alias including all exported members under one name 23 | 24 | ```ts 25 | import * as file from "./file"; 26 | // file.Test 27 | // file.Test2 28 | // file.constFunction() 29 | ``` 30 | 31 | As alternative classical `require` is the same: 32 | 33 | ```ts 34 | const file = require("./file"); 35 | ``` 36 | 37 | _`require` notation is not flexible and `import` is preferable_ 38 | 39 | Import value and change imported name 40 | 41 | ```ts 42 | import { Test as MyTest, Test2 as MyTest2 } from "./file"; 43 | ``` 44 | 45 | Name of the exported member could be changed: 46 | 47 | ```ts 48 | const abc: string = 100; 49 | export abc as otherName 50 | // later 51 | import { otherName } from './' 52 | ``` 53 | 54 | ## Default exports 55 | 56 | Default exports used when module should provide one value by default, and possibly multiple others. 57 | 58 | Using of default exports is not recommended due to not fixed naming on importing side. 59 | Also when changes in default exported member happened, all importing code will be affected 60 | 61 | ```ts 62 | // exp.ts 63 | interface MyType { 64 | a: number; 65 | } 66 | const myValue = { 67 | a: 10, 68 | }; 69 | export default myValue; 70 | // tmp.ts 71 | import myAnotherValue from "./exp"; 72 | ``` 73 | 74 | Better to use named exports 75 | 76 | ```ts 77 | export interface MyType { 78 | a: number; 79 | } 80 | export const myValue = { 81 | a: 10, 82 | }; 83 | // later 84 | import { MyType, myValue } from "./exp"; 85 | ``` 86 | 87 | Combine namespaces and destructuring 88 | 89 | ```ts 90 | import * as myFullModule, {MyType, myValue} from './exp' 91 | ``` 92 | 93 | There are much more ro read about import/export in [docs](https://www.typescriptlang.org/docs/handbook/modules.html) 94 | 95 | Many parts there are used for compatibility with javascript commonjs and AMD modules and could be tweaked by tsc options. 96 | -------------------------------------------------------------------------------- /docs/content/nodejs-lib.md: -------------------------------------------------------------------------------- 1 | # Node.js modules 2 | 3 | Node.js contains a lot of modules allowing ot perform most of required functions to work 4 | with filesystem, http, processes etc. 5 | 6 | Most of them provide basic functionality and it is more common nowadays to use 7 | special solutions to solve these tasks. 8 | 9 | As an example it is rarely used `http` (`https`) to make API requests, but rather `axios` 10 | which provide more nice options and packed with additional functionality 11 | 12 | You could install all Node.js modules type definitions: 13 | 14 | ```sh 15 | yarn add @types/node -D 16 | ``` 17 | 18 | Some popular modules: 19 | 20 | - `fs` module provides an API for interacting with the file system in a manner closely modeled around standard POSIX functions. 21 | 22 | - `crypto` provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions. 23 | 24 | - `Buffer` objects are used to represent binary data in the form of a sequence of bytes 25 | 26 | - `globals` objects which are available in all modules: `__dirname`, `__filename`, `require`, `console` 27 | 28 | - `cluster` allows easy creation of child processes that all share server ports. 29 | 30 | - `process` provides information about, and control over, the current Node.js process 31 | 32 | - `url` provides utilities for URL resolution and parsing. 33 | 34 | And much more 35 | 36 | ```ts 37 | import { unlinkSync } from "fs"; 38 | 39 | try { 40 | unlinkSync("/tmp/hello"); 41 | console.log("successfully deleted /tmp/hello"); 42 | } catch (err) { 43 | // handle the error 44 | } 45 | ``` 46 | 47 | Hint: `Cannot find module 'fs' or its corresponding type declarations.` error happening when no `@types/node` is installed 48 | 49 | ## fs 50 | 51 | Recommended filesystem access pattern: 52 | 53 | ```ts 54 | import { promises: fs } from "fs"; 55 | const contents = await fs.readFile(name); 56 | ``` 57 | 58 | No need to use `callback` or manual `Promise` wrapping 59 | -------------------------------------------------------------------------------- /docs/content/project.md: -------------------------------------------------------------------------------- 1 | # Typescript project 2 | 3 | ## nvm 4 | 5 | [Node Version Manager](https://github.com/nvm-sh/nvm) makes easier to install and use multiple `nodejs` versions on same computer. 6 | 7 | List versions and install: 8 | 9 | ```sh 10 | $ nvm ls-remote 11 | $ nvm install 12 12 | ``` 13 | 14 | By adding additional scripts into `.bashrc`(`.zshrc`) it could automatically pick up `.nvmrc` file in directory 15 | and switch to the specified version. 16 | 17 | `.nvmrc`: 18 | 19 | ```txt 20 | 12 // required version 21 | ``` 22 | 23 | ```sh 24 | $ nvm use 25 | Found '/..../.nvmrc' with version <12> 26 | Now using node v12.16.1 (npm v6.14.8) 27 | ``` 28 | 29 | # Project directory 30 | 31 | Usual project structure: 32 | 33 | ```txt 34 | .. 35 | .nvmrc 36 | index.ts 37 | node_modules // locally installed dependencies 38 | package.json // project 39 | yarn.lock // dependencies exact url and version (when using yarn) 40 | package-lock.json // dependencies exact url and version (when using npm) 41 | ``` 42 | 43 | Only one of `yarn.lock` or `package-lock.json` should be present. 44 | 45 | # npm 46 | 47 | Node package manager. 48 | 49 | It is package manager and "more" for javascript based projects. 50 | 51 | Also published to [npmjs registry](https://www.npmjs.com/package/npm) 52 | 53 | Some useful commands: 54 | 55 | ```txt 56 | install // download package locally 57 | publish // publish package to registry 58 | audit 59 | run // execute script from `package.json` / `scripts` 60 | ... 61 | ``` 62 | 63 | # yarn 64 | 65 | It is also package manager. 66 | 67 | And also published in [npmjs registry](https://www.npmjs.com/package/yarn) 68 | 69 | Some useful commands: 70 | 71 | ```sh 72 | $ yarn --help 73 | - add 74 | - audit 75 | - publish 76 | - upgrade-interactive 77 | ... 78 | ``` 79 | 80 | Add package: 81 | 82 | ```sh 83 | $ yarn add glob express // add into dependencies 84 | $ yarn add -D @types/glob @types/node // add into devDependencies 85 | ``` 86 | 87 | > `yarn` and `npm` provides almost similar functionality, you could choose any 88 | 89 | ### yarn upgrade-interactive 90 | 91 | One of the useful commands is to interactively upgrade packages: 92 | 93 | ```sh 94 | $ yarn upgrade-interactive --latest 95 | 96 | yarn upgrade-interactive v1.21.1 97 | info Color legend : 98 | "" : Major Update backward-incompatible updates 99 | "" : Minor Update backward-compatible features 100 | "" : Patch Update backward-compatible bug fixes 101 | ? Choose which packages to update. (Press to select, to toggle all, to invert selection) 102 | devDependencies 103 | name range from to url 104 | ◯ @types/jest latest 26.0.13 ❯ 26.0.14 https://github.com/DefinitelyTyped/DefinitelyTyped.git 105 | ◯ @types/node latest 14.10.2 ❯ 14.11.2 https://github.com/DefinitelyTyped/DefinitelyTyped.git 106 | ◯ aws-sdk latest 2.753.0 ❯ 2.758.0 https://github.com/aws/aws-sdk-js 107 | ◯ ts-jest latest 26.3.0 ❯ 26.4.0 https://kulshekhar.github.io/ts-jest 108 | ◯ typescript latest 4.0.2 ❯ 4.0.3 https://www.typescriptlang.org/ 109 | ``` 110 | 111 | # package.json 112 | 113 | Configuration for the current project. 114 | 115 | Can be generated by `npm init` or `yarn init` with asking some questions like name, licence, author. 116 | 117 | ```json 118 | { 119 | "name": "typescript-practices", 120 | "version": "1.0.0", 121 | "description": "Some description", 122 | "main": "index.js", 123 | "author": "Oleg Makiienko", 124 | "license": "ISC", 125 | "scripts": { 126 | "clean": "./scripts/clean.sh", 127 | "markdown": "./scripts/markdown.sh", 128 | "format": "prettier --write ." 129 | }, 130 | "dependencies": { 131 | "fs-extra": "^9.0.1", 132 | "glob": "^7.1.6" 133 | }, 134 | "devDependencies": { 135 | "prettier": "^2.1.2", 136 | "typescript": "^4.0.3", 137 | "@types/fs-extra": "^9.0.1", 138 | "@types/glob": "^7.1.3", 139 | "@types/node": "^14.11.1" 140 | } 141 | } 142 | ``` 143 | 144 | Could include multiple configurations for various tools. 145 | Structure should be in `json` format. 146 | Fields there are not limited to some list and any tool can put configuration there: 147 | 148 | ```json 149 | { 150 | "prettier": "... prettier.config.js", 151 | "eslintConfig": { 152 | "extends": "eslint-config-extension" 153 | }, 154 | "jest": { 155 | "preset": "...", 156 | "globals": { 157 | "ts-jest": { 158 | "tsConfig": "tsconfig.json" 159 | } 160 | }, 161 | "globalSetup": "@shelf/jest-dynamodb/setup.js", 162 | "globalTeardown": "@shelf/jest-dynamodb/teardown.js", 163 | "testEnvironment": "@shelf/jest-dynamodb/environment.js" 164 | } 165 | } 166 | ``` 167 | 168 | # Lock files 169 | 170 | ```txt 171 | yarn.lock // for yarn 172 | package-lock.json // for npm 173 | ``` 174 | 175 | This files are fixing exact url to package, its dependencies and hash sums. 176 | 177 | File updated each time new package adder to the project. 178 | 179 | Files should be committed into git. 180 | 181 | When building your application on CI/CD, exact versions of dependencies will be 182 | installed based on this file using: 183 | 184 | ```sh 185 | yarn install --frozen-lockfile 186 | ``` 187 | 188 | ## dependencies 189 | 190 | Described what packages are required for running application. 191 | 192 | Usually should not have testing utilities, types, dev tools 193 | 194 | To install only them, use `--production` argument: 195 | 196 | ```sh 197 | yarn install --production 198 | ``` 199 | 200 | ## devDependencies 201 | 202 | Includes everything "else" required for assembling and testing code, bit not required to run final application. 203 | 204 | `@types/xxx` packages should be in `devDependencies` section 205 | 206 | ## peerDependencies 207 | 208 | Those dependencies are required by current application, but installing them should be done within 209 | application using current package. 210 | 211 | # DefinitelyTyped 212 | 213 | This is special place where all javascript packages without types can have types defined by 214 | package authors or just community. 215 | 216 | 217 | 218 | It contains around 7_000 type definitions. 219 | 220 | As an example NPM package `glob` do not have typescript types so when importing it will not 221 | provide any IntelliSense: 222 | 223 | ```ts 224 | import * as glob from "glob"; // here we don't know what are inside 225 | ``` 226 | 227 | Additional package `@types/glob` can be installed to add those missing types 228 | 229 | ```ts 230 | import { Glob, IOptions } from "glob"; 231 | ``` 232 | 233 | `Glob` is exported and we can use it now and check exactly what `IOptions` it could accept in function calls 234 | -------------------------------------------------------------------------------- /docs/content/tsdocs.md: -------------------------------------------------------------------------------- 1 | # TSDocs 2 | 3 | As any other common language `typescript` has its preferred way to comment functions. 4 | 5 | It is `tsdoc` (by Microsoft) or community based `typedoc`. They are mostly compatible and helps to generate documentation out of specially formatted comments. 6 | 7 | - Tsdoc 8 | - Typedoc 9 | 10 | How it looks like: 11 | 12 | ```ts 13 | /** 14 | * Returns the average of two numbers. 15 | * 16 | * @remarks 17 | * This method is part of the {@link core-library#Statistics | Statistics subsystem}. 18 | * 19 | * @param x - The first input number 20 | * @param y - The second input number 21 | * @returns The arithmetic mean of `x` and `y` 22 | * 23 | * @beta 24 | */ 25 | public static getAverage(x: number, y: number): number { 26 | return (x + y) / 2.0; 27 | } 28 | ``` 29 | 30 | Mode samples are in [typedoc](https://typedoc.org/guides/doccomments/#supported-tags) documentation pages 31 | 32 | You could also use playground to try it out 33 | 34 | IntelliSense editor features are usually enabled when imported modules have tsdoc/jsdoc definitions 35 | 36 | > IntelliSense is a code-completion aid that includes a number of features: List Members, Parameter Info, Quick Info, and Complete Word. These features help you to learn more about the code you're using, keep track of the parameters you're typing, and add calls to properties and methods with only a few keystrokes. 37 | 38 | [IntelliSense in VSCode](https://docs.microsoft.com/en-us/visualstudio/ide/using-intellisense?view=vs-2019) 39 | -------------------------------------------------------------------------------- /docs/content/typescript.md: -------------------------------------------------------------------------------- 1 | # Typescript introduction 2 | 3 | Official documentation 4 | 5 | TypeScript stands in an unusual relationship to JavaScript. 6 | 7 | TypeScript offers all of JavaScript’s features, and an additional layer on top of these: TypeScript’s type system. 8 | 9 | # Usage 10 | 11 | Typescript code could be transpiled (also used as "compiled") into javascript to be run in browser or server-side. 12 | 13 | When transpiling into browser-compatible js it will include all polyfills required by targeted browsers. 14 | 15 | Commonly used scenarios: 16 | 17 | | Source Code | transpiled to javascript | Runtime | 18 | | ----------- | ------------------------ | ------------------------------------------------- | 19 | | Typescript | yes | Browser | 20 | | Typescript | yes | [Node.js](https://nodejs.org/en/) | 21 | | Typescript | yes | [Chakra](https://github.com/microsoft/ChakraCore) | 22 | | Typescript | no | [Deno](https://deno.land/) | 23 | 24 | ## Installation 25 | 26 | Typescript distributed as NPM package and could be installed by: 27 | 28 | `yarn add -g typescript` 29 | 30 | After this `tsc` will be available within project: 31 | 32 | ```sh 33 | $ yarn tsc index.ts 34 | ``` 35 | 36 | By default it will produce `index.js` file in same directory 37 | 38 | ## Configuration 39 | 40 | Typescript `tsc` could be configured using cli arguments ot using `tsconfig.json`. 41 | 42 | Default configuration file can be generated any time: 43 | 44 | ```sh 45 | $ yarn tsc --init 46 | ``` 47 | 48 | To use existing config file, use `-p` or `--project` cli arg 49 | 50 | ```sh 51 | $ tsc -p tsconfig.json 52 | ``` 53 | 54 | Generated file will have multiple commented lines explaining most commonly used compiler flags: 55 | 56 | ```json 57 | { 58 | "compilerOptions": { 59 | "target": "es5", 60 | "module": "commonjs", 61 | "strict": true, 62 | "esModuleInterop": true, 63 | "skipLibCheck": true, 64 | "forceConsistentCasingInFileNames": true 65 | } 66 | } 67 | ``` 68 | 69 | _Visit https://aka.ms/tsconfig.json to read more about this file_ 70 | 71 | Configuration file could extends another configuration file and override some properties: 72 | 73 | `tsconfig.json` 74 | 75 | ```json 76 | { 77 | "compilerOptions": { 78 | "target": "es5", 79 | "module": "commonjs", 80 | "strict": true, 81 | "skipLibCheck": false, 82 | "forceConsistentCasingInFileNames": true 83 | } 84 | } 85 | ``` 86 | 87 | Relaxed options in `tsconfig.development.json` 88 | 89 | ```json 90 | { 91 | "extends": "./tsconfig.json", 92 | "compilerOptions": { 93 | "strict": false, 94 | "skipLibCheck": true 95 | } 96 | } 97 | ``` 98 | 99 | Source files and ignored files can be also given explicitly: 100 | 101 | ```json 102 | { 103 | "compilerOptions": { 104 | "target": "es5" 105 | }, 106 | "include": ["src/**/*"], 107 | "exclude": ["node_modules", "**/*.spec.ts"] 108 | } 109 | ``` 110 | 111 | Some most commonly used: 112 | 113 | - `allowJs` allow to use `typescript` files together with `javascript`. It is very useful when doing migration of old js projects. 114 | - `target` EcmaScript version of current typescript. 115 | - `module` which modules system will have resulting files. 116 | - `outDir` where to put generated javascript files, useful when required to separate artifacts from source code. 117 | - `lib` specify global libraries to be used in code 118 | - `skipLibCheck` do not check types in external libraries 119 | 120 | And many [others](https://www.typescriptlang.org/docs/handbook/compiler-options.html) 121 | 122 | # Using compiler programmatically 123 | 124 | This enables developers to use typescript compiler in non-standard way 125 | 126 | `index.ts` 127 | 128 | ```ts 129 | import * as ts from "typescript"; 130 | 131 | const source = "let x: string = 'string'"; 132 | 133 | let result = ts.transpileModule(source, { 134 | compilerOptions: { module: ts.ModuleKind.CommonJS }, 135 | }); 136 | 137 | console.log(JSON.stringify(result)); 138 | ``` 139 | 140 | Run it 141 | 142 | ```sh 143 | $ yarn tsc index.ts && node index.js 144 | ``` 145 | 146 | Will output javascript code: 147 | 148 | ```json 149 | { "outputText": "var x = 'string';\n", "diagnostics": [] } 150 | ``` 151 | 152 | Read more [here](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) 153 | -------------------------------------------------------------------------------- /docs/content/useful-libraries.md: -------------------------------------------------------------------------------- 1 | # Useful libraries 2 | 3 | In javascript/typescript world almost for everything exists many libraries. 4 | You could check them at 5 | 6 | # lodash 7 | 8 | Contains multiple helpers for an array and object. 9 | 10 | 11 | 12 | Chunking arrays 13 | 14 | ```ts 15 | import _ from "lodash"; 16 | _.chunk([1, 2, 3, 4, 5], 2); /// [ [1,2], [3,4], [5] ] 17 | ``` 18 | 19 | - `_.sortBy` - order array of objects by some criteria 20 | - `_.flattenDeep` - flatten deep internal array into one `[1, [2, [3, [4]], 5]] => [1, 2, 3, 4, 5]` 21 | - `_.uniq` - removed duplicates `[2, 1, 2]=> [2, 1]` 22 | 23 | Any many others. 24 | 25 | `underscore` package has almost same functionality but types there is messed a bit. 26 | 27 | # axios 28 | 29 | Make API requests from browser or backend. 30 | 31 | 32 | 33 | ```ts 34 | const axios = require("axios"); 35 | ``` 36 | 37 | Use with callbacks 38 | 39 | ```ts 40 | axios 41 | .get("/user", { 42 | params: { 43 | ID: 12345, 44 | }, 45 | }) 46 | .then(function (response) { 47 | console.log(response); 48 | }) 49 | .catch(function (error) { 50 | console.log(error); 51 | }) 52 | .then(function () { 53 | // always executed 54 | }); 55 | ``` 56 | 57 | Use with async/await 58 | 59 | ```ts 60 | async function getUser() { 61 | try { 62 | const response = await axios.get("/user?ID=12345"); 63 | console.log(response); 64 | } catch (error) { 65 | console.error(error); 66 | } 67 | } 68 | ``` 69 | 70 | # uuid 71 | 72 | Generates `uuid` v1 and v4. 73 | 74 | 75 | 76 | ```ts 77 | import { v4 } from "uuid"; 78 | v4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' 79 | ``` 80 | 81 | # kafkajs 82 | 83 | Provides producer and consumer for Kafka. 84 | 85 | 86 | 87 | ```ts 88 | const { Kafka } = require("kafkajs"); 89 | 90 | const kafka = new Kafka({ 91 | clientId: "my-app", 92 | brokers: ["kafka1:9092", "kafka2:9092"], 93 | }); 94 | ``` 95 | 96 | Produce messages: 97 | 98 | ```ts 99 | const producer = kafka.producer(); 100 | await producer.connect(); 101 | await producer.send({ 102 | topic: "test-topic", 103 | messages: [{ value: "Hello KafkaJS user!" }], 104 | }); 105 | 106 | await producer.disconnect(); 107 | ``` 108 | 109 | Consume messages: 110 | 111 | ```ts 112 | const consumer = kafka.consumer({ groupId: "test-group" }); 113 | await consumer.connect(); 114 | await consumer.subscribe({ topic: "test-topic", fromBeginning: true }); 115 | 116 | await consumer.run({ 117 | eachMessage: async ({ topic, partition, message }) => { 118 | console.log({ 119 | value: message.value.toString(), 120 | }); 121 | }, 122 | }); 123 | ``` 124 | 125 | Supports `SSL`, `SASL` and other Auth [approaches](https://kafka.js.org/docs/configuration) 126 | 127 | ```ts 128 | const fs = require("fs"); 129 | new Kafka({ 130 | clientId: "my-app", 131 | brokers: ["kafka1:9092", "kafka2:9092"], 132 | ssl: { 133 | rejectUnauthorized: false, 134 | ca: [fs.readFileSync("/my/custom/ca.crt", "utf-8")], 135 | key: fs.readFileSync("/my/custom/client-key.pem", "utf-8"), 136 | cert: fs.readFileSync("/my/custom/client-cert.pem", "utf-8"), 137 | }, 138 | }); 139 | ``` 140 | 141 | # aws-sdk 142 | 143 | AWS Client for every service provided 144 | 145 | ```sh 146 | npm install aws-sdk 147 | ``` 148 | 149 | Sample usage 150 | 151 | ```ts 152 | import AWS, { DynamoDB } from "aws-sdk"; 153 | 154 | const docClient = new DynamoDB.DocumentClient({ 155 | region: "eu-west-1", 156 | }); 157 | 158 | const result = await docClient 159 | .put({ 160 | TableName: "my-table", 161 | Item: { 162 | userId: 1, 163 | }, 164 | }) 165 | .promise(); 166 | ``` 167 | 168 | - `DynamoDB` has plain methods to work with `Attribute`s approach: `{userId: {N: '1'}}` 169 | - `DynamoDB.DocumentClient` has methods to work with dynmodb and use regular ts/js objects:`{userId: 1` 170 | 171 | # ajv 172 | 173 | `json-schema` validator. 174 | 175 | 176 | 177 | Given json schema 178 | 179 | `schema.json`: 180 | 181 | ```json 182 | { 183 | "type": "object", 184 | "additionalProperties": false, 185 | "required": ["userId", "email"], 186 | "properties": { 187 | "userId": { "type": "string", "pattern": "^[0-9]{1,}$" }, 188 | "email": { "type": "string", "format": "email" }, 189 | "age": { "type": "number", "min": 18, "max": 200 } 190 | } 191 | } 192 | ``` 193 | 194 | Json request object can be validated using this schema: 195 | 196 | ```ts 197 | import Ajv, { ErrorObject } from "ajv"; 198 | import itemSchema from "./schema.json"; 199 | 200 | interface PartialData { 201 | userId: string; 202 | email: string; 203 | age?: number; 204 | } 205 | 206 | type ItemValidationResult = Either< 207 | ErrorObject[] | undefined, 208 | PartialData | undefined 209 | >; 210 | 211 | export interface Validator { 212 | validateItem: (data: Record) => ItemValidationResult; 213 | } 214 | 215 | /** 216 | * Validation wrapper to perform ajv `compile` only once 217 | */ 218 | export const initValidator = (): Validator => { 219 | const ajv = new Ajv({ allErrors: true, messages: true }); 220 | const itemValidator = ajv.compile(itemSchema); 221 | 222 | const validateItem = (data: Record): ItemValidationResult => { 223 | const validationResult = itemValidator(data); 224 | return validationResult ? [undefined, data as PartialData] : itemValidator.errors] 225 | }; 226 | 227 | return { 228 | validateItem, 229 | }; 230 | }; 231 | const initValidator = initValidator() 232 | const [errors, result] = initValidator.validateItem({ 233 | userId: 'a' 234 | }) 235 | ``` 236 | 237 | `errors` will contain extended information about all errors in validated payload including missing properties, 238 | wrong types, values not matching minimum or maximum values, etc. Errors are pretty verbose 239 | 240 | # fs-extra 241 | 242 | Provides missing parts for standard bundled `fs` module 243 | 244 | 245 | 246 | Contains high level functions like `ensureDir` (recursively ensure directory exists), `copy` (copy directory with files) etc. 247 | 248 | # fp-ts 249 | 250 | Provides developers with popular patterns and reliable abstractions from typed functional languages in TypeScript. 251 | 252 | ```ts 253 | import { Option, isNone, none, some } from "fp-ts/lib/Option"; 254 | ``` 255 | 256 | Read more at 257 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Typescript Practices 2 | 3 | This is opinionated documentation giving introduction into `typescript`language and beyond. 4 | 5 | Idea of its creation is to give fast onboarding and knowledge refresh for the commonly used approaches and practices 6 | in day-to-day work. 7 | 8 | ### Typescript Introduction 9 | 10 | | | Includes | 11 | | ----------------------------------------------------------- | -------------------------------------------------- | 12 | | [Typescript Introduction](./content/typescript.md) | Typescript, tsc, `tsconfig.json`, programmatic use | 13 | | [Project structure and package tools](./content/project.md) | npm, yarn, `package.json`, DefinitelyTyped | 14 | | [Linting And Formatting](./content/lint-format.md) | `eslint`, `prettier`, `@typescript-eslint` | 15 | 16 | ### Typescript Language 17 | 18 | | | Includes | 19 | | ------------------------------------------------------------- | ------------------------------------------------------------------------- | 20 | | [Variables](./pages/language/variables.md)\* | let, const, scoping | 21 | | [Basic types](./pages/language/basic-types.md)\* | common types | 22 | | [Type & interface](./pages/language/type-interface.md) \* | definition, recursive, combining | 23 | | [Destructuring and spread](./pages/language/dest-spread.md)\* | for: tuples, array, objects | 24 | | [Function](./pages/language/function.md) \* | type definitions for regular, async , curried functions. `this` capturing | 25 | | [Modules](./content/modules.md) | import, export, default export | 26 | | [Callbacks](./pages/language/callbacks.md) \* | usage, maintenance complexity | 27 | | [Promise](./pages/language/promise.md) \* | chaining, error handling, parallel processing | 28 | | [async/await](./pages/language/async-await.md) \* | async definitions, error handling | 29 | | [Classes](./pages/language/classes.md) \* | members definitions | 30 | | [Iterate Array/Object](./pages/language/iterate.md) \* | for..of, for..in, Object alternatives | 31 | 32 | ### Advanced topics 33 | 34 | | | Includes | 35 | | ---------------------------------------------------- | ----------------------------------------- | 36 | | [Generics](./pages/language/generics.md) \* | function, type, interface | 37 | | [Utility Types](./pages/language/utility-types.md)\* | Partial, Required, Record, Pick, Omit etc | 38 | | [Node.js](./content/nodejs-lib.md) | Node.js modules and globals | 39 | | [TSDocs](./content/tsdocs.md) | source code documentation | 40 | | [Async arrays](./pages/topics/array-async.md) \* | async functions in `map`, `reduce`, etc | 41 | | [Custom Errors](./pages/topics/errors.md) \* | Error, extend Error, catch | 42 | 43 | ### Practical Concepts 44 | 45 | | | Includes | 46 | | ------------------------------------------------------------- | ------------------------------------------------------------------ | 47 | | [Class replacement](./pages/topics/replace-class.md) \* | use builder function instead of `class` definition | 48 | | [Union type instead of enum](./pages/topics/union-enum.md) \* | explanation when it is better to use union types instead of `enum` | 49 | | [Useful libraries](./content/useful-libraries.md) | walk thru some commonly used libraries | 50 | | [jest](./content/jest.md) | writing tests, configuration, mocks, expectations, `jest-dynamodb` | 51 | | [AWS Lambda](./content/lambda.md) | `@types/aws-lambda`, env, caching, DI, aws-sdk faking for tests | 52 | 53 | ### Notes 54 | 55 | Generated pages index: 56 | 57 | [Typescript Practices](./pages/index.md) 58 | 59 | `*` pages are fully generated from code, and could be examined in your favorite editor as one typescript file. Check them [here](https://github.com/omakoleg/typescript-practices/tree/master/src/language) 60 | 61 | Some code samples include code playground links (bottom right) to so you could 62 | test it without leaving browser 63 | 64 | [Privacy Policy](./privacy.md) 65 | -------------------------------------------------------------------------------- /docs/pages/index.md: -------------------------------------------------------------------------------- 1 | # Pages 2 | 3 | - [language/async await.md](./language/async-await.md) 4 | 5 | - [language/basic types.md](./language/basic-types.md) 6 | 7 | - [language/callbacks.md](./language/callbacks.md) 8 | 9 | - [language/classes.md](./language/classes.md) 10 | 11 | - [language/dest spread.md](./language/dest-spread.md) 12 | 13 | - [language/function.md](./language/function.md) 14 | 15 | - [language/generics.md](./language/generics.md) 16 | 17 | - [language/iterate.md](./language/iterate.md) 18 | 19 | - [language/promise.md](./language/promise.md) 20 | 21 | - [language/type interface.md](./language/type-interface.md) 22 | 23 | - [language/utility types.md](./language/utility-types.md) 24 | 25 | - [language/variables.md](./language/variables.md) 26 | 27 | - [topics/array async.md](./topics/array-async.md) 28 | 29 | - [topics/errors.md](./topics/errors.md) 30 | 31 | - [topics/replace class.md](./topics/replace-class.md) 32 | 33 | - [topics/union enum.md](./topics/union-enum.md) 34 | -------------------------------------------------------------------------------- /docs/pages/language/async-await.md: -------------------------------------------------------------------------------- 1 | # Async 2 | 3 | The next iteration on async processing improvement. 4 | 5 | Functions are defined as `async` and executed with `await`. 6 | 7 | When `Promise` is awaited, resolver value will be returned and rejected thrown. 8 | 9 | ```ts 10 | const makeApiRequest = async (id: number): Promise => 11 | Promise.resolve(id); 12 | 13 | async function asyncExecution(): Promise { 14 | const first = await makeApiRequest(1); 15 | const second = await makeApiRequest(2); 16 | const third = await makeApiRequest(3); 17 | console.log(first + second + third); 18 | } 19 | asyncExecution(); 20 | ``` 21 | 22 | In Node.js async functions called in main scope and "awaited" by runtime 23 | 24 | `await` could be used with `async` function, function returning `Promise` or literal. 25 | 26 | > Generally `await` for literals should not be used ! 27 | 28 | This introduces errors and could be checked by `await-thenable` rule in `eslint-typescript` 29 | 30 | Here is classical issue when working with `aws-sdk`: 31 | 32 | ```ts 33 | import { DynamoDB } from "aws-sdk"; 34 | const dynamo = new DynamoDB(); 35 | 36 | const saveRecord = (data: any) => 37 | dynamo.putItem({ 38 | TableName: "xx", 39 | Item: data, 40 | }); 41 | 42 | async function criticalBug() { 43 | const data = {}; 44 | await saveRecord(data); 45 | console.log(`Item saved`); 46 | // Actually it was not yet saved 47 | // await waited for callback function. 48 | } 49 | criticalBug(); 50 | ``` 51 | 52 | Correctly will be to await for Promise: 53 | 54 | ```ts 55 | const saveRecordCorrect = (data: any): Promise => 56 | dynamo 57 | .putItem({ 58 | TableName: "xx", 59 | Item: data, 60 | }) 61 | .promise(); 62 | ``` 63 | 64 | Will be also useful to enable `eslint` rule to require return types on functions: `@typescript-eslint/explicit-function-return-type` 65 | 66 | This will require to specify `Promise` as return type explicitly and 67 | potentially error will not happen 68 | 69 | ## Async errors handling 70 | 71 | Errors are captured with try-catch or propagated into surrounding scope. 72 | This way wrapping code will catch all errors thrown within this code. 73 | 74 | Important to remember: Error will be thrown at a context where `await` is used. 75 | 76 | This is very common misconception 77 | 78 | ```ts 79 | const makeApiRequestRejection = async (): Promise => 80 | Promise.reject(1000); 81 | 82 | async function function1(): Promise { 83 | try { 84 | return await makeApiRequestRejection(); 85 | } catch (e: unknown) { 86 | console.error(`Api error in function1`); 87 | throw e; 88 | } 89 | } 90 | 91 | async function function2(): Promise { 92 | try { 93 | return await function1(); 94 | } catch (e: unknown) { 95 | console.error(`Api error in function2`); 96 | throw e; 97 | } 98 | } 99 | function2(); 100 | ``` 101 | 102 | Error will have a way thru all layers: 103 | 104 | - "Api error in function1" 105 | - "Api error in function2" 106 | - "(node:88361) UnhandledPromiseRejectionWarning: 1000" ... 107 | 108 | ```ts 109 | async function updatedFunction1(): Promise { 110 | try { 111 | return makeApiRequestRejection(); 112 | } catch (e: unknown) { 113 | console.error(`Api error in function1`); 114 | throw e; 115 | } 116 | } 117 | async function updatedFunction2(): Promise { 118 | try { 119 | return await updatedFunction1(); 120 | } catch (e: unknown) { 121 | console.error(`Api error in function2`); 122 | throw e; 123 | } 124 | } 125 | updatedFunction2(); 126 | ``` 127 | 128 | Here `updatedFunction1` returns function result without `await` 129 | 130 | Error will first appear only in surrounding block 131 | 132 | - "Api error in function2" 133 | - "(node:88361) UnhandledPromiseRejectionWarning: 1000" ... 134 | 135 | Try-catch in `updatedFunction1` will not be used to handle error 136 | 137 | # Key takeaways 138 | 139 | - Use `try-catch` if `await`-ed value can throw errors. 140 | - Do not `await` for literal values, only thenable. 141 | - Always explicitly provide returned type in async function definition 142 | -------------------------------------------------------------------------------- /docs/pages/language/basic-types.md: -------------------------------------------------------------------------------- 1 | # Any 2 | 3 | Could be disabled by compiler flag `--noImplicitAny` 4 | 5 | ```ts 6 | const anyValue: any = {}; 7 | ``` 8 | 9 | # Unknown 10 | 11 | Better not to use it explicitly 12 | 13 | Recently was added as a type for an error in `catch` 14 | 15 | ```ts 16 | const maybe: unknown = {}; 17 | 18 | const maybeSomething = {} as unknown; 19 | 20 | try { 21 | throw 42; 22 | } catch (err: unknown) {} 23 | ``` 24 | 25 | # Void 26 | 27 | Usually used to define that function do not return any value 28 | 29 | ```ts 30 | function none(): void {} 31 | function log(line: string): void { 32 | console.log(line); 33 | } 34 | ``` 35 | 36 | # String 37 | 38 | ```ts 39 | const str: string = "1"; // '111' 40 | const strTwo: string = `123`; 41 | ``` 42 | 43 | # Boolean 44 | 45 | ```ts 46 | const yes: boolean = true; 47 | ``` 48 | 49 | # Symbol 50 | 51 | Always unique, in practice `enum` is more adopted 52 | 53 | ```ts 54 | let sym1 = Symbol("something"); 55 | let symbolYes1 = Symbol("yes"); 56 | let symbolYes2 = Symbol("yes"); 57 | ``` 58 | 59 | symbolYes1 === symbolYes2 // false 60 | 61 | # Numeric 62 | 63 | ```ts 64 | let num: number = 6; 65 | ``` 66 | 67 | Could have "\_" separators to increase readability 68 | 69 | ```ts 70 | let readableNumber: number = 5_000_000_000; 71 | ``` 72 | 73 | Could be defined directly with oct/bin/hex literals 74 | 75 | ```ts 76 | let hex: number = 0xf00d; 77 | let binary: number = 0b1010; 78 | let octal: number = 0o744; 79 | ``` 80 | 81 | Available starting from `ES2020` (`tsconfig.json "target": "es2020"`) 82 | 83 | ```ts 84 | let big: bigint = 10000000000000000000000000000n; 85 | ``` 86 | 87 | Sometimes you will need this when looking for a maximum number in an array 88 | 89 | ```ts 90 | let maxNumber = Infinity; 91 | ``` 92 | 93 | # Arrays 94 | 95 | Could be defined by `[]` or `Array` generic 96 | 97 | ```ts 98 | const array: any[] = ["a", "b"]; 99 | const anotherArray: Array = [1, 2]; 100 | ``` 101 | 102 | 2 levels array definition 103 | 104 | ```ts 105 | const arrayComplex: string[][] = [ 106 | ["a", "b"], 107 | ["c", "d"], 108 | ]; 109 | ``` 110 | 111 | Arrays could mix different types. This is not very practical 112 | 113 | ```ts 114 | const mixedArray: (number | string | boolean)[] = [1, "2", true]; 115 | const strangeArray: (number | number[])[] = [1, [1]]; 116 | ``` 117 | 118 | # Tuple 119 | 120 | Do not confuse with `Array` 121 | 122 | ```ts 123 | const sampleTuple: [string, number, boolean] = ["a", 1, true]; 124 | ``` 125 | 126 | # Enum 127 | 128 | Without explicit values provided. This will by default apply numbers sequence starting from `0` in transpiled javascript code 129 | 130 | ```ts 131 | enum Status { 132 | OK, 133 | FAILURE, 134 | } 135 | const myStatus: Status = Status.OK; 136 | ``` 137 | 138 | With explicit values 139 | 140 | ```ts 141 | enum Counter { 142 | ONE = "a", 143 | TWO = "b", 144 | THREE = "c", 145 | } 146 | const myNumber: Counter = Counter.TWO; 147 | ``` 148 | 149 | # Undefined, null 150 | 151 | Undefined is usually used to define implicitly that nothing is there, it is empty, not defined 152 | 153 | Pure undefined 154 | 155 | ```ts 156 | let undef: undefined; 157 | const data1: undefined = [].find((x) => x > 0); 158 | ``` 159 | 160 | To represent "maybe" case, when value possibly is not set 161 | 162 | ```ts 163 | const data2: undefined | number = [1].find((x) => x > 0); 164 | ``` 165 | 166 | Usually is used for explicit "not set" but better to use `undefined` 167 | 168 | ```ts 169 | let _null: null = null; 170 | ``` 171 | 172 | # Never 173 | 174 | Used in functions that will definitely not return 175 | 176 | ```ts 177 | function explode(): never { 178 | throw new Error("bam"); 179 | } 180 | ``` 181 | 182 | # Object 183 | 184 | Everything else except number, string, boolean, symbol, null, or undefined 185 | 186 | Generally, you won’t need to use this. 187 | 188 | `Object` type is like `any` amongst objects 189 | 190 | ```ts 191 | const data3: Object = {}; 192 | ``` 193 | 194 | # Function 195 | 196 | Please do not use `Function` type explicitly, it is like `any` amongst functions 197 | 198 | ```ts 199 | const func: Function = () => 1; 200 | ``` 201 | -------------------------------------------------------------------------------- /docs/pages/language/callbacks.md: -------------------------------------------------------------------------------- 1 | # Callbacks 2 | 3 | Callbacks allowing to utilise so called "async nature" of `javascript` (and `typescript`) by detaching current 4 | execution and continue it later. 5 | 6 | Defined in a way that function except data parameters have also `callback` as last parameter. 7 | 8 | Callback is a function with 2 parameters: 9 | 10 | - `error` on a first place to reflect case when some issues happened, usually second parameter will not have value in this case 11 | - `data` useful function result, in this case `err` usually is set explicitly to `null` or `undefined` 12 | 13 | Return type of both mentioned functions is `void`. Because no-one is waiting for them to return value synchronous. 14 | 15 | Callback still can use `return` inside, but it is used to stop its execution and exit. 16 | 17 | ```ts 18 | import fs from "fs"; 19 | fs.readFile( 20 | "filename", 21 | (err: NodeJS.ErrnoException | null, data: Buffer): void => { 22 | if (err) { 23 | return console.error(`Error occur: ${err}`); 24 | } 25 | console.log(`Data ${data.toString()}`); 26 | } 27 | ); 28 | ``` 29 | 30 | In this example callback function will be executed "after" file descriptor is returned by OS and file contents were read out. 31 | 32 | Function doing division, and returning an error when someone will try to divide by `0` 33 | 34 | ```ts 35 | const divide = ( 36 | a: number, 37 | b: number, 38 | cb: (err?: Error, result?: number) => void 39 | ) => { 40 | try { 41 | if (b === 0) { 42 | return cb(new Error(`Cant divide by 0`)); 43 | } 44 | cb(undefined, a / b); // this returns `Infinity` 45 | } catch (e: unknown) { 46 | if (e instanceof Error) cb(e); 47 | return cb(Error((e as any).toString())); 48 | } 49 | }; 50 | const callback = (err?: Error, result?: number) => { 51 | if (err) { 52 | return console.log("error happened", err); 53 | } 54 | console.log("result", result); 55 | }; 56 | divide(1, 1, callback); // prints: "result 1" 57 | divide(1, 0, callback); // prints: "error happened Error: Cant divide by 0" 58 | ``` 59 | 60 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAJgSwG4LgUxgXhgCgFAwwCGAXDGAK4C2ARmgE4A0BMNZltDzhwbuD9APxkAovXogmMemggUANlGHlqdegEosAPhhIQqPJsw6A3iyj0AnjDOFCCAGa4aWTNgAMm23cIyoFejAYXhwwNAB3GDEJehwAAwBhIjBYRBR0Vmt3OPV1AG4WQgBfQuCaHAowdAcEMLhGYhgAelZ85paoAAsECGk0f0DeuIBJMBqwBChLOJYi4KIoYE7+MkqAazAQcLAvUsd+GFroZOA0ECdoyU0QtHzSvwCgkMvYnAwiXuTLdQA6KBAAMoWWoAcxwuTuxTwRQKoEgsGARHk8hoRGAayw-HEyheDRkckUyg4aiMphY+ze4l2PgegWC4AgIHkaB+8hAYIARAJJDBOkQAA78tB1DkNASQmAlHgMpkstmc-EKKCivoEqB3GF4NKoNA4ACMDQN82RqPRbSaLX59FqUAgZA5isUMD1HK1yB1+oa7gaiJNaLW5st1pSdpgXPEPL5guFaDgUQj9DISRS8HdGRoWQ5QA) 61 | 62 | When many callbacks are used it is easy to mess-up things and produce complex code 63 | 64 | ```ts 65 | import { stat, readFile, writeFile } from "fs"; 66 | 67 | type Err = NodeJS.ErrnoException | null; 68 | const fileToUpdate = "/tmp/some-file.txt"; 69 | 70 | stat(fileToUpdate, (err: Err, stats: any) => { 71 | if (err) { 72 | return console.error(`Stats error: ${err}`); 73 | } 74 | console.log(`File stats: ${stats}`); 75 | readFile(fileToUpdate, (errRead: Err, contents: any) => { 76 | if (errRead) { 77 | return console.error(`File reading error: ${errRead}`); 78 | } 79 | const newContents = `${contents}-updated`; 80 | // throw new Error(`Nobody will handle me`) 81 | writeFile(fileToUpdate, newContents, (errWrite: Err) => { 82 | if (errWrite) { 83 | return console.error(`File writing error: ${errRead}`); 84 | } 85 | console.log(`Write finished`); 86 | }); 87 | }); 88 | }); 89 | ``` 90 | 91 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgZxgQxgGjlApmgEwDFgAbXbAdymBlxPLgF84AzKCEOAIleW4DcAKCEwAnmFxwAolChwAvHAByEArgBSAZQB0sqADsI0gB4BjXGBjAIBuAB84BgK6lSws7dRsyuACoQAKpgBBhSStwA9DDgkcicuAC0rL46MCYwgiKoGAAUKeQBwaF02Lm4cgBcMnLYOTDI1WgGYgCUigB8iEJwcMCscOVy7Qg9vTi4MM6GcJ4G8eQ6FRxQuQAGWugNcMvQ1QAkCMtMa63CvUxjcwu4OqQQAObrDFL1jXCHbydnY3iEL-lfEUQmEyssAEr4AjVfTYOZ0AwNJotdoKLqjcZ9AZDKCQwgjMaYvBTGbXCCLXarNYvCaEYAGB47OR7D5HOR4gjfc7jS6Y67wAy4SgAYVsCO2SjWh3huERyCYiWcILoBDW3N6kUicBgAAsOJQnEKait1qoAEZqMRwShkUhwHXNAiMEC4U6E600OgAgr+ILKiiGkVi2UNMFyADqntwMOGnW6mN6-UGy0jtFwBIT42J0zsZIpzKpNOotHpjMpBzZuKhXPdF1rsy85Nu9yea1TdB8BmAyB1uFVP0xTAHzB+Q4EQA) 92 | 93 | Maintaining such code is complex and easy to make mistakes 94 | 95 | Currently unhandled errors will be just logged to stderr, if they are not given to callbacks. 96 | -------------------------------------------------------------------------------- /docs/pages/language/classes.md: -------------------------------------------------------------------------------- 1 | # Class definitions 2 | 3 | Originally to make classes, was used "prototype-based inheritance" when new functions were added into function prototype. 4 | 5 | This was complex and brought a lot of confusion. 6 | 7 | Staring from `ECMAScript 2015` classes were introduced 8 | 9 | ```ts 10 | type LogLevel = "error" | "debug"; 11 | class Logger { 12 | level: LogLevel; 13 | 14 | constructor(level: LogLevel) { 15 | this.level = level; 16 | } 17 | 18 | log(message: string) { 19 | console.log(`${this.level}: ${message}`); 20 | } 21 | } 22 | 23 | const logger = new Logger("debug"); 24 | logger.log("Test message"); 25 | ``` 26 | 27 | [open code in online editor](https://www.typescriptlang.org/play?#code/C4TwDgpgBAMg9gcxhAbhANlAvFARBAJwLgNygB88ATCAIwFcFcBuAKAGN0BDAZx9kQJCUAN6soUdKgwAuAUmno24qOzgA7HsAL12wEgAopadHPgKTASlEqJwABYBLHgDpjGbJMVsJAX1Yq6IgGALYQfFxCcloEjuoI1mISEmqacFJuwQAGACQiDs5uir5yeWERQr5Zlj5Q-v4cGlqSgsI46hAA7vJCBAa4NAxMNaxBCL2ZCP0AKuHAUOU8kRC4I0A) 28 | 29 | Try to use functional approaches always. 30 | Your code should not have `class` definitions, believe you don't need them. 31 | 32 | Inheritance in classes 33 | 34 | ```ts 35 | class BaseLogger { 36 | name: string; 37 | constructor(prefix: string) { 38 | this.name = prefix; 39 | } 40 | getFullMessage(message: string) { 41 | return `${this.name}: ${message}`; 42 | } 43 | } 44 | 45 | class ExtendedLogger extends BaseLogger { 46 | constructor(name: string) { 47 | super(name); // call base class constructor 48 | } 49 | getFullMessage(message: string) { 50 | //call base version of `getFullMessage` 51 | return `Extended: ${super.getFullMessage(message)}`; 52 | } 53 | } 54 | 55 | const extendedLogger = new ExtendedLogger("SampleLog"); 56 | console.log(extendedLogger.getFullMessage("some message")); // Extended: SampleLog: some message 57 | ``` 58 | 59 | [open code in online editor](https://www.typescriptlang.org/play?#code/FAYwNghgzlAEBC0CmAZA9gcw0gTrA3sLLAHYQC2SAXLFAC44CWJGA3EbCGifTgK4g6aHAAoADjiQAzRgA8avZhgCUBDsToALRlAB0ZSrAC8sCdLntiAXw7Y6AMT5gwAWSQwI2EZQ-YFDJVVCYmJJOj4cElgAAwASfC0dfQokKxp4nyhPVOjLWBsbUEgYWABRWTokEgATJGr0LFxYJAqq6rhEKFRMbDxgzm5eASFRA2paAJYg9Vo+MVwRMeVWWAB6Vc4IZ1gAI2ROYrguHgZh4Q4bYjtHZzdfJG93LL8Jpim1ELXVkC2wXf2AG64KCMbiwNBSGLXJyuJ7ZaIzMIRKLRcqVGp1dL4KBzXC6aG3OFeTLZZRWXIXYCFY70ZqtDH1HpNEwkJAAdzK9NqjMaogARABlChiMDdDB85agQZoUW6MCYEQtdHchq9fFIBwwu7PB58qBoQwk7AS5ZfTnKzGwIXkEVihQGpCwI1IIA) 60 | 61 | Class members definition 62 | 63 | ```ts 64 | class PublicPrivate { 65 | // public by default 66 | name0: string = "test"; 67 | // private 68 | #name: string = "test"; 69 | // private 70 | private name2 = "test3"; 71 | // public 72 | public name3 = "test3"; 73 | // readonly (public) 74 | readonly canBeSetInConstructorOnly: boolean; 75 | 76 | constructor() { 77 | this.canBeSetInConstructorOnly = true; 78 | } 79 | private get() {} 80 | set() {} 81 | i_am_using_all() { 82 | this.#name; 83 | this.name2 = `${this.name2}something`; 84 | } 85 | } 86 | 87 | const publicPrivate = new PublicPrivate(); 88 | publicPrivate.name0; // ok 89 | publicPrivate.name3; // ok 90 | publicPrivate.set(); // ok 91 | ``` 92 | 93 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYGwhgzhAEAKCuAjEBLYsBOKBuYAuAptAN4BQ00A9JdAA5KrDSICe0AJgQGZjwh7loAOzABbAgAYAXNAh4sQgObQAvNABEhOeoDcg6nSy5CggMQjxMuQuVrNBbXooHaR-AUGuc74WIIAmVQ0tPABmXX0aemQ0TwY0X3FQoPs5cKcqGgwCMHYAeyEQNgAKaMYASkFs3IKi6GAwIQAhAgBlAjwASSEAYQLreGA8PIwAeUKWGUQ8vJAcoT1BYH75QeGMYvKSQQo8AAsUCAA6Bua2ju6+oQGhkfG6tVWCDIBfTzdCaEUOzZI3iggPy2xH+0BQAH0xOD4BAUEpISAQL8yBRdgdjuY-Bk0YcjhYAkEAAYAEmI+1x+P8LwgeXE5KUhNepDepGW1zwdHi6A+RDUQgIAHc4FzMN5CJs9GU0KLjAQ8X4JDpMtA8gBrUhS7liuX40JKgxqjUinlHQF4CXKw1AA) 94 | -------------------------------------------------------------------------------- /docs/pages/language/dest-spread.md: -------------------------------------------------------------------------------- 1 | # Destructuring 2 | 3 | Generally it is operation when complex object split into pieces and assigned to variables 4 | 5 | ## Tuples 6 | 7 | ```ts 8 | const tuple: [string, number, boolean] = ["test", 1, true]; 9 | ``` 10 | 11 | All elements 12 | 13 | ```ts 14 | const [e1, b2, c3] = tuple; 15 | ``` 16 | 17 | taking only some of elements 18 | 19 | ```ts 20 | const [firstOnly] = tuple; 21 | const [, secondElement] = tuple; 22 | const [first, , last] = tuple; 23 | ``` 24 | 25 | Not really convenient and easy to make "commas" mistake when trying to take only last element 26 | 27 | ```ts 28 | const [, , last_only] = tuple; 29 | ``` 30 | 31 | In combination with `spread` operator could take part of tuple into another tuple 32 | 33 | `tail` will be tuple: `[number, boolean]` 34 | 35 | ```ts 36 | const [head, ...tail] = tuple; 37 | ``` 38 | 39 | Common use case: return multiple values from a function 40 | 41 | ```ts 42 | const someValidation = (): [Object?, string[]?] => [{}]; 43 | const [result, maybeErrors] = someValidation(); 44 | ``` 45 | 46 | For arrays destructuring looks the same 47 | 48 | ```ts 49 | const numbersArray = [1, 2, 3, 4, 5]; 50 | const [arrayHead, ...arrayTail] = numbersArray; 51 | ``` 52 | 53 | # Object destructuring 54 | 55 | Allows to assign one or more values based on object properties 56 | 57 | ```ts 58 | interface SplitIntoPieces { 59 | error: string; 60 | data: number; 61 | probably?: number; 62 | } 63 | const { error, data, probably } = {} as SplitIntoPieces; 64 | ``` 65 | 66 | Destructuring deep properties 67 | 68 | ```ts 69 | interface DestroyMeDeep { 70 | message: string; 71 | data: { 72 | a: string; 73 | b: number; 74 | }; 75 | } 76 | const { 77 | data: { a, b }, 78 | } = {} as DestroyMeDeep; 79 | ``` 80 | 81 | Destructuring with changing parameter name 82 | 83 | ```ts 84 | const { 85 | message: someMessage, 86 | data: { a: aOther }, 87 | } = {} as DestroyMeDeep; 88 | ``` 89 | 90 | Destructuring: Common use case 91 | Provide Dependency container to your application and pick services when they needed 92 | 93 | ```ts 94 | interface ApplicationConfig { 95 | database: Object; 96 | api: Object; 97 | logger: Object; 98 | config: Object; 99 | } 100 | const applicationConfigInstance = { 101 | database: {}, 102 | api: {}, 103 | logger: {}, 104 | config: {}, 105 | }; 106 | const { api } = applicationConfigInstance; 107 | ``` 108 | 109 | # Spread operator 110 | 111 | ## In functions 112 | 113 | It is opposite of destructuring 114 | 115 | Spread of function arguments from the tuple. 116 | All parameters are required and tuple has fixed length, it is perfect match 117 | 118 | ```ts 119 | const args: [number, number, string] = [1, 2, "3"]; 120 | function spreadTuple(a: number, b: number, c: string): void {} 121 | spreadTuple(...args); 122 | ``` 123 | 124 | From an array. 125 | 126 | Array has more elements, so remaining will be ignored. 127 | 128 | Also due to array variable nature all function parameters should be optional and match type 129 | 130 | ```ts 131 | const args2 = [1, 2, 3, 4]; 132 | function spreadAccept(a?: number, b?: number): void {} 133 | spreadAccept(...args2); 134 | ``` 135 | 136 | Spread as function parameter 137 | 138 | `console.log` is the most common example of this practice 139 | 140 | ```ts 141 | function acceptAll(...strings: string[]): void {} 142 | acceptAll("a", "b", "c"); 143 | ``` 144 | 145 | ## In objects 146 | 147 | Could add properties from one object into another (merge). 148 | 149 | Usually used as alternative to `Object.assign()` 150 | 151 | ```ts 152 | interface BaseProperties { 153 | a: number; 154 | b: number; 155 | c: number; 156 | } 157 | interface ExtendedProperties extends BaseProperties { 158 | run: () => void; 159 | } 160 | const dataObject = { 161 | a: 1, 162 | b: 2, 163 | c: 3, 164 | }; 165 | const extendedDataObject: ExtendedProperties = { 166 | run: () => console.log("Running"), 167 | ...dataObject, 168 | }; 169 | ``` 170 | 171 | Important to keep spreads order. 172 | 173 | Often used to define `default` values 174 | 175 | ```ts 176 | const defaultUser = { 177 | name: "default name", 178 | email: "default email", 179 | }; 180 | const apiRequestData = { 181 | name: "Test", 182 | age: 21, 183 | }; 184 | const resultingUser = { 185 | userId: "abc", 186 | ...defaultUser, 187 | ...apiRequestData, 188 | }; 189 | ``` 190 | 191 | will result in: 192 | 193 | ```ts 194 | const resultingUserShape = { 195 | userId: "abc", 196 | name: "Test", 197 | age: 21, 198 | email: "default email", 199 | }; 200 | ``` 201 | 202 | Spread deep properties 203 | 204 | Each object should be specified explicitly like `address` here 205 | 206 | ```ts 207 | interface DeepProperties { 208 | name: string; 209 | names: string[]; 210 | age: number; 211 | address: { 212 | country: string; 213 | city: string; 214 | street: string; 215 | }; 216 | } 217 | const userA: DeepProperties = {} as DeepProperties; 218 | const userB: DeepProperties = {} as DeepProperties; 219 | 220 | const resultingUserAB: DeepProperties = { 221 | ...userA, 222 | ...userB, 223 | // merge arrays manually 224 | names: [...userA.names, ...userB.names], 225 | // merge "deep" objects manually 226 | address: { 227 | ...userA.address, 228 | ...userB.address, 229 | }, 230 | }; 231 | ``` 232 | -------------------------------------------------------------------------------- /docs/pages/language/function.md: -------------------------------------------------------------------------------- 1 | # Function definition 2 | 3 | Function parameters could be defined using required or optional notations 4 | 5 | ```ts 6 | function withOptionalArgs(a: string, b?: number): void {} 7 | function withOptionalDefaultArgs(a: string, b = 10): void {} 8 | ``` 9 | 10 | Optional parameters should be in the end of parameters definitions. 11 | 12 | Could be used spread operator to capture all parameters into an array 13 | 14 | ```ts 15 | function withPassThruArgs(...other: string[]): void {} 16 | ``` 17 | 18 | Function can have another function as parameter 19 | 20 | ```ts 21 | function convertToNumber( 22 | data: string, 23 | cb: (error: string | undefined, result?: number) => void 24 | ): void {} 25 | ``` 26 | 27 | Same rules applied to anonymous functions 28 | 29 | ```ts 30 | const withOptionalArgs2 = (a: string, b?: number): void => {}; 31 | const withOptionalDefaultArgs2 = (a: string, b = 10): void => {}; 32 | const withPassThruArgs2 = (a: string, ...other: string[]): void => {}; 33 | ``` 34 | 35 | Anonymous functions could be explicitly typed 36 | 37 | ```ts 38 | type LoggerFunction = (...params: string[]) => void; 39 | const logFunction: LoggerFunction = (...params: string[]) => { 40 | console.log(...params); 41 | }; 42 | const errorFunction: LoggerFunction = (...params: string[]) => { 43 | console.error(...params); 44 | }; 45 | ``` 46 | 47 | Function type can also be defined as `interface` 48 | 49 | This is not popular way, please don't do this, exists for backwards compatibility with javascript 50 | 51 | ```ts 52 | interface FunctionType { 53 | (a: string): string; 54 | } 55 | const stringInterfaceFunction: FunctionType = (a: string) => a.toLowerCase(); 56 | ``` 57 | 58 | Could be applied destructuring to the function parameters 59 | 60 | ```ts 61 | interface FunctionParams { 62 | a: string; 63 | b: number; 64 | } 65 | const appInParams = (first: number, { a }: FunctionParams): void => { 66 | console.log(first, a); 67 | }; 68 | ``` 69 | 70 | Destructuring with optional parameters and applied defaults 71 | 72 | Parameters order doesn't matter in this case. Optional default is not necessary at last position 73 | 74 | ```ts 75 | interface PartialFunctionOptionalObject { 76 | b: number; 77 | c?: string; 78 | d?: string; 79 | } 80 | const partialParamsOptional = ( 81 | a: number, 82 | { b, c = "defined", d }: PartialFunctionOptionalObject 83 | ): void => { 84 | console.log(a, b, c, d); // d = string | undefined, c = string 85 | }; 86 | ``` 87 | 88 | Provide default value when it is possible. 89 | 90 | ```ts 91 | const functionWithoutDefault = (name: string, data?: string[]) => { 92 | if (data) { 93 | // 'data' can be used now 94 | data.map((x) => x); 95 | } 96 | // OR this way: 97 | (data || []).map((x) => x); 98 | }; 99 | 100 | const functionWithDefault = (name: string, data: string[] = []) => { 101 | // 'data' can be used now 102 | }; 103 | ``` 104 | 105 | # Async functions 106 | 107 | As a good practice is to use `await `inside of `async` function otherwise it is just a function returning `Promise`. 108 | 109 | This is ensured by `eslint` rules, typescript itself allows `async` function without `await` 110 | 111 | ```ts 112 | const mainAsync = async (a: number): Promise => { 113 | return await Promise.resolve(a); 114 | }; 115 | const mainPromise = (): Promise => { 116 | return Promise.resolve(1); 117 | }; 118 | ``` 119 | 120 | Using any of these 121 | 122 | ```ts 123 | async function myFunction() { 124 | await mainAsync(1); // async function 125 | await mainPromise(); // Promise function 126 | } 127 | ``` 128 | 129 | In Node.js you could call `async` function on top level and runtime will internally wait it to be resolved 130 | 131 | ```ts 132 | myFunction(); 133 | ``` 134 | 135 | # Curried functions 136 | 137 | Functions returning functions. 138 | 139 | They used to keep some context till later time to execute action within that context 140 | 141 | ```ts 142 | type LongCurriedFunction = (a: string) => (code: number) => number; 143 | ``` 144 | 145 | It is better to define all intermediate types to increase readability 146 | 147 | ```ts 148 | type InternalFunction = (code: number) => number; 149 | type WrapperFunction = (a: string) => InternalFunction; 150 | 151 | const wrapperFunction: WrapperFunction = (a: string) => { 152 | console.log("wrapper", a); 153 | return (justCode: number) => { 154 | console.log("internal function", a, justCode); 155 | return justCode + 1; 156 | }; 157 | }; 158 | ``` 159 | 160 | Call one then second 161 | 162 | ```ts 163 | const partialFunction = wrapperFunction("test"); // => InternalFunction 164 | partialFunction(200); // => 201 165 | ``` 166 | 167 | Call both same time. Pretty useless scenario 168 | 169 | ```ts 170 | wrapperFunction("test")(200); // => 201 171 | ``` 172 | 173 | # Short notation 174 | 175 | Can be used when function have only one operation which gives result and it could be immediately returned 176 | 177 | > Effectively it removes `{}` and `return` 178 | 179 | ```ts 180 | const shortNotation = (a: string, b: number) => `${a} + ${b}`; 181 | 182 | const shortCurried = (a: string) => (b: number) => (c: string) => 183 | `${a} -> ${b} -> ${c}`; 184 | ``` 185 | 186 | Return an object as only one operation in function 187 | 188 | ```ts 189 | const addAndReturn = (a: string, b: string) => ({ 190 | sum: a + b, 191 | }); 192 | ``` 193 | 194 | Here, extra `()` is required because `{}` in function definition used to define function body. 195 | 196 | Here are same function definitions: add1, add2 197 | 198 | ```ts 199 | function add1(a: number, b: number) { 200 | return a + b; 201 | } 202 | const add2 = (a: number, b: number) => a + b; 203 | add1(1, 2); 204 | add2(1, 2); 205 | ``` 206 | 207 | # "this" capturing 208 | 209 | Main difference between `function` and `const` function definitions is the way how they works with `this` 210 | 211 | In this sample `set` and `setClassical` doing the same thing, but are different 212 | because of definitions used 213 | 214 | ```ts 215 | function practiceThisWrapper() { 216 | // Curried function type 217 | type Setter = (a: string) => (a: string) => void; 218 | // Object type definition in needed for `this` usage inside of it's functions 219 | interface CapturingObject { 220 | data: Record; 221 | set: Setter; 222 | setClassical: Setter; 223 | } 224 | const thisCapturingObject: CapturingObject = { 225 | data: {}, 226 | set: function (key: string) { 227 | // Next arrow function is capturing `this` as current scope 228 | // classical `function` will not work when using `this` inside 229 | return (value: string) => { 230 | this.data[key] = value; 231 | }; 232 | }, 233 | setClassical: function (key: string) { 234 | // keep `this` for the `function` 235 | const self = this; 236 | return function (value: string) { 237 | self.data[key] = value; 238 | }; 239 | }, 240 | // It is wrong definition: 241 | // `this` will be referencing scope of `thisPracticeWrapper` 242 | // setShort: (key: string) => (value: string) => { 243 | // this.data[key] = value; 244 | // }, 245 | }; 246 | thisCapturingObject.set("data")("value"); 247 | thisCapturingObject.setClassical("data2")("value2"); 248 | console.log(thisCapturingObject.data); 249 | // { data: 'value', data2: 'value2' } 250 | } 251 | practiceThisWrapper(); 252 | ``` 253 | 254 | As a recommendation here is to use builder to create the object. 255 | This will not require to use `this` and will be more transparent 256 | 257 | ```ts 258 | function practiceThisBuilderWrapper() { 259 | // Curried function type 260 | type Setter = (a: string) => (a: string) => void; 261 | // Object type definition in needed for `this` usage inside of it's functions 262 | interface CapturingObject { 263 | data: Record; 264 | set: Setter; 265 | setClassical: Setter; 266 | } 267 | const buildCapturingObject = (): CapturingObject => { 268 | const data: Record = {}; 269 | // both are `arrow function` definitions 270 | const set = (key: string) => { 271 | return (value: string) => { 272 | data[key] = value; 273 | }; 274 | }; 275 | // both are `function` definitions 276 | function setClassical(key: string) { 277 | return function (value: string) { 278 | data[key] = value; 279 | }; 280 | } 281 | return { 282 | data, 283 | set, 284 | setClassical, 285 | }; 286 | }; 287 | const thisCapturingObject = buildCapturingObject(); 288 | thisCapturingObject.set("data")("value"); 289 | thisCapturingObject.setClassical("data2")("value2"); 290 | console.log(thisCapturingObject.data); 291 | // { data: 'value', data2: 'value2' } 292 | } 293 | practiceThisBuilderWrapper(); 294 | ``` 295 | -------------------------------------------------------------------------------- /docs/pages/language/generics.md: -------------------------------------------------------------------------------- 1 | # Generics 2 | 3 | Usual use cases: 4 | 5 | - when actual type is not important 6 | - when solution should be reusable for some cases 7 | 8 | ## Function Generics 9 | 10 | Placeholders `TYPE` and `O` could be named any way, preferable to have one capital letter. 11 | 12 | Generic function type to be reusable 13 | 14 | ```ts 15 | function logSomething(data: TYPE, param: O): void {} 16 | const logSomethingAgain = (data: TYPE): void => {}; 17 | ``` 18 | 19 | pass-thru generic function `wrapper`: 20 | 21 | ```ts 22 | const handler = (data: any): void => {}; 23 | const wrapper = (param: T) => { 24 | handler(param); 25 | }; 26 | ``` 27 | 28 | # Type Generic 29 | 30 | Optional type defined this way 31 | 32 | ```ts 33 | type Optional = T | undefined; 34 | type OptionalNumber = Optional; 35 | ``` 36 | 37 | Could have 2 values 38 | 39 | ```ts 40 | const maybeInt: OptionalNumber = 1; 41 | const maybeAnotherOne: OptionalNumber = undefined; 42 | ``` 43 | 44 | # Interface Generic 45 | 46 | ```ts 47 | interface GenericTree { 48 | value: L; 49 | left?: GenericTree; 50 | right?: GenericTree; 51 | } 52 | const stringTree: GenericTree = { 53 | value: "h", 54 | left: { 55 | value: "b", 56 | right: { 57 | value: "c", 58 | }, 59 | }, 60 | }; 61 | const numberTree: GenericTree = { 62 | value: 10, 63 | left: { 64 | value: 3, 65 | right: { 66 | value: 8, 67 | }, 68 | }, 69 | }; 70 | console.log(stringTree); 71 | console.log(numberTree); 72 | ``` 73 | 74 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgOIRNYCAqUIQA8AMgHzIDeAUMsgG5wA2ArhAFzLEDcNyjEMMAH4O6TFGx4CJUj1oSA5gAthojFlz4iZHgF8qCAPYgAzmGRmJIBVPZp1EzdMugF5ALyVeDFnYBESn4ANLz8ghzUtLQ+rBx+AEbBvPLAymARyVEx-ghJUci6IbSFVLo8RqbmIMwAtvHQtmriklqE1XXQHl7RTLHIAIwADEV8AundWb12AMwjKWkZ+T2+HAAccwUjJWUGxiaG-AB0jIYKABQu1rYAlOV7BxDHp2ft9VA3PEA) 75 | 76 | Using multiple type placeholders 77 | 78 | ```ts 79 | type TwoPlaceholdersType = (input: A) => B; 80 | ``` 81 | 82 | Make type aliasing 83 | 84 | ```ts 85 | type Converter = (input: A) => B; 86 | 87 | type ConvertToNumber = Converter; 88 | type ConvertToString = Converter; 89 | 90 | const toNumberConverter: ConvertToNumber = (input: string): number => 91 | parseInt(input); 92 | 93 | const toStringConverter: ConvertToString = (input: number): string => 94 | input.toString(); 95 | 96 | console.log(toNumberConverter("100")); 97 | console.log(toStringConverter(200)); 98 | ``` 99 | 100 | [open code in online editor](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gOwG4QE7DQHgIIBooBCAfFALxQAUAlgmAK7ABcU2AlGSQQNwBQPokWIhToAKnABydALYAjNGSHI0GVJgDOwVDQDm+BDPmoivAdHjKxcAMpbdiiyNWYDctPk3aEOk3wDGiJpQwJKGaI4qaMwRVlJuqIrUtAzMnrqszK5GHDxQUGAAhqjqEACSCMBJ9MCsvDwBCEEhtl46MarRwiriLfbkVSlQWWgZUGneOXk01QB0zXbeFLX+gXAANhAza3A6FCFxRu1oFABEAIwADBcnrMsN6uub27vzrUeoFABMV7dcQA) 101 | 102 | Generics based on type boundaries. 103 | 104 | Here `array: A[]` showing that array methods like `.map`, `.reduce` etc will be available 105 | 106 | ```ts 107 | const arrayMap = (array: A[], func: (x: A) => B): B[] => array.map(func); 108 | ``` 109 | 110 | Custom type as generic type base 111 | 112 | `` showing that `T` should be subtype of `Sizable` 113 | 114 | ```ts 115 | interface Sizable { 116 | size: number; 117 | } 118 | 119 | interface SizableCloth { 120 | name: string; 121 | size: number; 122 | } 123 | 124 | interface SizableHat { 125 | radius: number; 126 | size: number; 127 | } 128 | 129 | const sumAllSizes = (array: T[]): number => 130 | array.reduce((p: number, c: T) => c.size + p, 0); 131 | const hat: SizableHat = { 132 | size: 5, 133 | radius: 1, 134 | }; 135 | const cloth: SizableCloth = { 136 | size: 10, 137 | name: "cool", 138 | }; 139 | const resultSum = sumAllSizes([hat, cloth]); // => 15 140 | console.log(resultSum); 141 | ``` 142 | 143 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMrAF5wEYBsUDeAUMsgM6YQBcyIArgLbbQDcRAvkUaJLIiuix4IAYVwB7MAAtkxUiDgNq5MFFABzNqQoZl9Jqw5ce0eEjSYc+ABJwwsksihwAJsDpka+5lC3lKXow+bJxECOIgZPZkjACCuLiCEGTIALzIADwAKsgQAB6QIC4pglYQAHwAFHBQzgCeNFkA2gC6AJSBBlBp5Y419QB0UBAudEiVlQAOnT4ANMgIjW09CwM6KADUyJPzAAxtbOGR9lJ2NKXCtvbpcv66NACss47Obh40AIzP7IcRUQsSaTnSzCMSSGQ3RzrT67Z7yRTKABE4XEuER31+xycyTouDAqEYaXIcQSSTIlSapzA8wQgKk7RYyAA9EyVh8HmE-qiIAMJOpKsMYniCQwDkQgA) 144 | 145 | Generic type definition based on another generic type 146 | 147 | Index Type Query or `keyof`: It yields the type of permitted property names for a give type. 148 | `extends keyof` will result in having `K` as string with values as property names of `O` 149 | 150 | `O[K]` will be type of the value accessible in `O` when using `K` property 151 | 152 | ```ts 153 | const getProperty = (obj: O, key: K): O[K] => obj[key]; 154 | ``` 155 | 156 | Here `a` have type `number` so result of `getProperty` will the `number` 157 | 158 | ```ts 159 | const numberProp: number = getProperty({ a: 1 }, "a"); 160 | ``` 161 | 162 | Here `b` have type `string` so result of `getProperty` will the `string` 163 | 164 | ```ts 165 | const stringProp: string = getProperty({ a: 1, b: "1" }, "b"); 166 | ``` 167 | -------------------------------------------------------------------------------- /docs/pages/language/iterate.md: -------------------------------------------------------------------------------- 1 | # Iterate array items 2 | 3 | ## For-of 4 | 5 | Iterate over "values" in the given array 6 | 7 | ```ts 8 | const array1 = ["one", "two", "three"]; 9 | for (let entry of array1) { 10 | console.log(entry); 11 | // "one" 12 | // "two" 13 | // "three" 14 | } 15 | ``` 16 | 17 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXAngRhgXhgbQETgCmeANDHlAO4inlQAWChxAugNwBQAZiAjABQAbQrEJgoCFDBBd4SVGgCUMAN4cYMUJBDCAdIJABzfmIkpFnDQHor5InnUwbdag+u2KjZg4C+QA) 18 | 19 | ## For-in 20 | 21 | Iterate over "keys" in the given array 22 | 23 | ```ts 24 | const array2 = ["one", "two", "three"]; 25 | for (let entry in array2) { 26 | console.log(entry, typeof entry); 27 | // "0" "string" 28 | // "1" "string" 29 | // "2" "string" 30 | } 31 | ``` 32 | 33 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXAngJhgXhgbQETgCmeANDHlAO4inlQAWChxAugNwBQAZiAjABQAbQrEJgoCFDACWYeElRoAlDADeHGDFCQQwgHSCQAc35iJKMlBQAHQiC4wzkpZ00B6N+QAMectASyRngaMB7kAIy+eP6Bwe6eeGhRMWBBHAC+QA) 34 | 35 | ## Iterate key-value 36 | 37 | The `.entries()` method returns a new Array Iterator object that contains the key/value pairs for each index in the array. 38 | 39 | ```ts 40 | const array3 = ["one", "two", "three"]; 41 | for (let [key, value] of array3.entries()) { 42 | console.log(key, value, typeof key, typeof value); 43 | // 0, "one", "number", "string" 44 | // 1, "two", "number", "string" 45 | // 2, "three", "number", "string" 46 | } 47 | ``` 48 | 49 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhgJwXAngZhgXhgbQETgCmeANDHlAO4inlQAWChxAugNwBQAZiAjABQAbQrBwBrQijIA3OIICuhFjBBd4SVGgB0hMFAQBLQhH4BKUzADeHGDFCQQwrYJABzfhKkxZCwmSgoAA6EqjCe-kEhaj6Kppy2APQJMAAMZOREtORg8gC2AEaECFl40IZgrng2MEkwAIzpFNQlOQVFJWUGFVWJyQBMjQxMxI2thcWNnd0cAL5AA) 50 | 51 | # Objects 52 | 53 | ## For-of 54 | 55 | To have the same approach as for an array, use `Object.values()` to iterate over values 56 | 57 | ```ts 58 | const obj1 = { one: 10, two: 20, three: 30 }; 59 | for (let v of Object.values(obj1)) { 60 | console.log(v); 61 | // 10 62 | // 20 63 | // 30 64 | } 65 | ``` 66 | 67 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBGArAjDAvDA3nMBTAXDMgAwA0MUA7iIQExkUAWATrgTAMzEwC+A3ACgAZiGYwAFABtcsAG5whMAPJJcwKADpZAQ0kBXXBHEIUASlNYBMGKEghpGySADm42acHWA9F6LErMD4w9AFBXAI8QA) 68 | 69 | ## For-in 70 | 71 | Same as for arrays, iterate over keys 72 | 73 | ```ts 74 | const obj2 = { one: 10, two: 20, three: 30 }; 75 | for (let k in obj2) { 76 | console.log(k); 77 | // "one" 78 | // "two" 79 | // "three" 80 | } 81 | ``` 82 | 83 | Can be used `Object.keys()` to transform obejct keys into the array and later can be iterated with `for-of` 84 | 85 | ## Iterate key-value 86 | 87 | Objects dont have function `entries`, but it is available via: `Object.entries()` 88 | 89 | ```ts 90 | const obj3 = { one: 10, two: 20, three: 30 }; 91 | for (let [k, v] of Object.entries(obj3)) { 92 | console.log(k, v, typeof k, typeof v); 93 | // "one", 10, "number", "string" 94 | // "two", 20, "number", "string" 95 | // "three", 30, "number", "string" 96 | } 97 | ``` 98 | 99 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBGArAzDAvDA3nMBTAXDAIwAMANDFAO4iEBM5lAFgE64EzIkwC+A3ACgAZiBYwAFABtcsANoBrCgDcAunCEwA8klzAoAOlxgoLAJa4I4hCgCUNrAJgxQkENP2SQAc3GKYSiigATwAHXBANP2CwiP8bQScAekSYACJwXFSKUgo0sABXAFt4XBYsp1ToMzAvVMcYZLTqEHKGXNSC4tLytKrTGrqklNSoVnZyrnbOkrL2voGBHgEgA) 100 | -------------------------------------------------------------------------------- /docs/pages/language/promise.md: -------------------------------------------------------------------------------- 1 | # Using Promise 2 | 3 | `Promise` can be either `pending` or `fulfilled` or `rejected`. `fulfilled` and `rejected` are representing results. 4 | 5 | Every promise should eventually be resolved or rejected 6 | 7 | ```ts 8 | const promiseSample: Promise = Promise.resolve(1); 9 | ``` 10 | 11 | Dummy result wrappers can be created directly 12 | 13 | ```ts 14 | Promise.resolve(1); 15 | Promise.reject(1); 16 | ``` 17 | 18 | Constructor 19 | 20 | ``` 21 | new (executor: ( 22 | resolve: (value?: T | PromiseLike) => void, 23 | reject: (reason?: any) => void) => void 24 | ): Promise; 25 | ``` 26 | 27 | ```ts 28 | new Promise( 29 | (resolve: (data: number) => void, reject: (reason: any) => void) => { 30 | try { 31 | resolve(1 + 1); 32 | } catch (e) { 33 | reject(e); 34 | } 35 | } 36 | ); 37 | ``` 38 | 39 | Promise transforms callback approach into chaining of error or result handlers 40 | 41 | ```ts 42 | import axios from "axios"; 43 | axios 44 | .get("/user", { 45 | params: { 46 | ID: 12345, 47 | }, 48 | }) 49 | .then((response) => { 50 | // process response 51 | console.log(response); 52 | }) 53 | .catch((error) => { 54 | // process error 55 | console.log(error); 56 | }) 57 | .then(() => { 58 | // always executed 59 | }); 60 | ``` 61 | 62 | Chain calls 63 | 64 | ```ts 65 | Promise.resolve(1) 66 | .then((result) => result + 1) 67 | .then((result) => { 68 | console.log(result); // 2 69 | return result; 70 | }) 71 | .then((result) => result + 1) 72 | .then((result) => { 73 | console.log(result); // 3 74 | return result; 75 | }); 76 | ``` 77 | 78 | Catching errors in one place for all `then` cases 79 | 80 | ```ts 81 | Promise.resolve(1) 82 | .then((result) => result + 1) 83 | .then((result) => result / 0) 84 | .then((result) => result * 1) 85 | .catch((error) => { 86 | console.error(error); 87 | }); 88 | ``` 89 | 90 | Catching expected errors in the middle of the processing 91 | 92 | ```ts 93 | Promise.resolve(1) 94 | .then((result) => result / 0) 95 | .catch((error) => { 96 | // catch division by zero 97 | console.error(error); 98 | // it will be Promise.resolve(0) 99 | // so `.then` chaining could be used again 100 | return 0; 101 | }) 102 | .then((result) => result + 1) 103 | .then((result) => result * 1); 104 | ``` 105 | 106 | Chaining promise functions 107 | 108 | ```ts 109 | interface UserShape { 110 | userId: number; 111 | name: string; 112 | } 113 | const loadUser = (userId: number): Promise => 114 | Promise.resolve({ 115 | userId: 1, 116 | name: "Some user", 117 | }); 118 | 119 | const capitalizeUser = (user: UserShape): Promise => 120 | Promise.resolve({ 121 | ...user, 122 | name: user.name.toUpperCase(), 123 | }); 124 | 125 | const logUser = (user: UserShape): Promise => { 126 | console.log(user); 127 | return Promise.resolve(user); 128 | }; 129 | 130 | const sendUser = (user: UserShape): Promise => axios.post("url", user); 131 | 132 | loadUser(42) 133 | .then(capitalizeUser) 134 | .then(logUser) 135 | .then(sendUser) 136 | .catch(console.error); 137 | ``` 138 | 139 | Transforming callback function into Promise based 140 | 141 | ```ts 142 | const plusOneCallback = ( 143 | value: number, 144 | cb: (err: Error | undefined, result: number) => void 145 | ) => cb(undefined, value + 1); 146 | 147 | const plusOnePromise = (value: number) => 148 | new Promise((resolve, reject) => { 149 | plusOneCallback(value, (error, data) => { 150 | if (error) { 151 | return reject(error); 152 | } 153 | resolve(data); 154 | }); 155 | }); 156 | 157 | plusOnePromise(42).then(console.log); 158 | ``` 159 | 160 | ## Promise helper functions 161 | 162 | ### Promise.all 163 | 164 | Returns a single Promise that resolves to an array of the results of the input promises 165 | 166 | > It rejects immediately upon any of the input promises rejecting 167 | 168 | ```ts 169 | const promise1 = Promise.resolve(3); 170 | const promise2 = 42; 171 | const promise3 = Promise.resolve(2); 172 | 173 | Promise.all([promise1, promise2, promise3]).then((values) => { 174 | console.log(values); // [3, 42, 2] 175 | }); 176 | ``` 177 | 178 | Rejection in Promise.all() 179 | 180 | ```ts 181 | const promise11 = Promise.resolve(3); 182 | const promise22 = Promise.reject(42); 183 | const promise33 = new Promise((resolve) => { 184 | setTimeout(() => resolve(100), 100); 185 | }); 186 | 187 | Promise.all([promise11, promise22, promise33]) 188 | .then((values) => { 189 | console.log(values); 190 | }) 191 | .catch(console.error); // => 42 192 | ``` 193 | 194 | ### Promise.allSettled 195 | 196 | Method returns a promise that resolves after all of the given promises have either fulfilled or rejected, 197 | with an array of objects that each describes the outcome of each promise. 198 | 199 | > You usually want to use `.allSettled` instead of `.all` 200 | 201 | ```ts 202 | function settledExample() { 203 | const promise100 = Promise.resolve(3); 204 | const promise200 = new Promise((resolve, reject) => { 205 | setTimeout(() => reject(100), 100); 206 | }); 207 | Promise.allSettled([promise100, promise200]).then((results) => { 208 | console.log(results); 209 | // [ 210 | // { status: 'fulfilled', value: 3 }, 211 | // { status: 'rejected', reason: 100 } 212 | // ] 213 | const finishedValues = results 214 | .filter((x) => x.status === "fulfilled") 215 | .map((x) => (x as PromiseFulfilledResult).value); 216 | console.log(finishedValues); // [ 3 ] 217 | }); 218 | } 219 | settledExample(); 220 | ``` 221 | 222 | ### Promise.race 223 | 224 | Method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, 225 | with the value or reason from that promise. 226 | 227 | ```ts 228 | function raceSample() { 229 | const apiCallSuccess = new Promise((res, rej) => 230 | setTimeout(() => res("Done"), 100) 231 | ); 232 | const apiCallFailure = new Promise((res, rej) => 233 | setTimeout(() => rej("Error"), 100) 234 | ); 235 | const timeoutPromise = (ms = 300) => 236 | new Promise((res, rej) => setTimeout(rej, ms)); 237 | 238 | const requestWithTimeout = (request: Promise, timeout = 300) => 239 | Promise.race([timeoutPromise(timeout), request]); 240 | 241 | requestWithTimeout(apiCallSuccess).then(console.log); 242 | requestWithTimeout(apiCallFailure).catch(console.error); 243 | } 244 | raceSample(); 245 | ``` 246 | 247 | # More info 248 | 249 | Check [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 250 | -------------------------------------------------------------------------------- /docs/pages/language/type-interface.md: -------------------------------------------------------------------------------- 1 | # Type 2 | 3 | Simple type aliasing 4 | 5 | ```ts 6 | type CustomerName = string; 7 | ``` 8 | 9 | Useful when function have many parameters and it is easy to make mistakes when passing them. Just extract `string` as domain specific type 10 | 11 | ```ts 12 | const customerName: CustomerName = "Oleg"; 13 | ``` 14 | 15 | Mixed types 16 | 17 | ```ts 18 | type MaybeString = string | undefined; 19 | type SomethingNobodyExpects = string | number | boolean; 20 | ``` 21 | 22 | Represent multiple possibilities when defining error 23 | 24 | ```ts 25 | type Errors = string | string[] | undefined; 26 | ``` 27 | 28 | Type defined out of fixed literals 29 | 30 | ```ts 31 | type FixedNumbers = 1 | 2 | 3 | 4; 32 | type FixedStrings = "ONE" | "TWO" | "THREE"; 33 | ``` 34 | 35 | Boolean looks strange, but still used 36 | 37 | ```ts 38 | type IamAlwaysTrue = true; 39 | type IamAlwaysFalse = false; 40 | ``` 41 | 42 | Complex object defined as new type 43 | 44 | ```ts 45 | type SomethingBigger = { 46 | a: number; 47 | b: number; 48 | }; 49 | ``` 50 | 51 | # Recursive types 52 | 53 | Could include itself in definitions 54 | 55 | ```ts 56 | type Tree = { 57 | value: number; 58 | left?: Tree; 59 | right?: Tree; 60 | }; 61 | const tree: Tree = { 62 | value: 10, 63 | left: { 64 | value: 5, 65 | right: { 66 | value: 7, 67 | }, 68 | }, 69 | }; 70 | console.log(tree); 71 | ``` 72 | 73 | [open code in online editor](https://www.typescriptlang.org/play?#code/C4TwDgpgBAKgThaBeKBvAUFKA3AhgGwFcIAuKAO0IFsAjCOAbkynwgDNgB+M+RJrOAEsA5gAsuPBBCYBfJgGMA9uQDOwKMCmTEUFBix4ipKAEYADABpmrDmX1YcBYmQCsVh1CFjgd5h8POUADs7g4yoeHocuhKqoqsAHT4isIAFJqIAJQMQA) 74 | 75 | # Combining type definitions 76 | 77 | Done with `|` (OR) or `&` (AND) 78 | 79 | `MyError` could be any of listed 80 | 81 | ```ts 82 | class MyErrorClass {} 83 | type MyError = Error | MyErrorClass; 84 | ``` 85 | 86 | And case `&` used to merge types into one 87 | 88 | ```ts 89 | type WithNumbers = { 90 | one: number; 91 | two: number; 92 | }; 93 | type WithStrings = { 94 | three: string; 95 | four: string; 96 | }; 97 | type CombinedObject = WithNumbers & WithStrings; 98 | const combined: CombinedObject = { 99 | one: 1, 100 | two: 2, 101 | three: "3", 102 | four: "4", 103 | }; 104 | console.log(combined.one); 105 | console.log(combined.three); 106 | ``` 107 | 108 | [open code in online editor](https://www.typescriptlang.org/play?#code/C4TwDgpgBA6glsAFgOQK4FsBGEBOBnKAXigG8AoKKAewDsIAuKGjbHAbgqmAHcrHmsuDgF8OoSLASIAysBxwaAcwLFylJDggMoeOQsUdKAMyqocjXfKUix4aAGEqWBRAAmAeUwArCAGNgRJJIaIL4UABkQTJ6SngcvrS6UAnOdK6Mjqlunj7+gWrUdIwAjAA0nDx8UABM5eqImtoARADMTXVQJmaMTQAs7WSiZAk0eFQANhAAdONUigAUKZgurlO0EACU8YkT07MLSytTGlpbQA) 109 | 110 | All properties with same name will have resulting type `never`. Do not do this ! 111 | 112 | # Type vs Interface 113 | 114 | Type generally is used for one liners, simple cases with `|` and `&` or functions 115 | 116 | Interface is used for complex constructs 117 | 118 | ```ts 119 | type GoodType = string | string[] | undefined; 120 | type GoodFunctionType = (a: string, b: string) => string; 121 | interface GoodDataInterface { 122 | customerId: number; 123 | age: number; 124 | email: string; 125 | } 126 | ``` 127 | 128 | # Interface 129 | 130 | Could have optional properties 131 | 132 | ```ts 133 | interface WithOptionalProps { 134 | definitelyHere: number; 135 | goodDefinedOptional?: string; // prefer this way to define optional 136 | notSoGood: string | undefined; 137 | } 138 | ``` 139 | 140 | Represent partially undefined shape. 141 | `userId` should be there and everything else could present but not required to know how it is looks like 142 | 143 | ```ts 144 | interface JsonDecodedData { 145 | userId: string; 146 | [anyNameHere: string]: any; 147 | } 148 | ``` 149 | 150 | Usually keys are of type`string`. `number` is also allowed but it is not convenient to use it. 151 | 152 | Common use case is to parse json body and pass it to next service 153 | 154 | ```ts 155 | const body = `{"userId": "1", "age":21, "name":"Bob"}`; 156 | const apiRequest = JSON.parse(body) as JsonDecodedData; 157 | if (apiRequest.userId !== undefined) { 158 | console.log(apiRequest); 159 | } 160 | ``` 161 | 162 | # Extend interface 163 | 164 | ```ts 165 | interface Base { 166 | length: number; 167 | } 168 | interface Coordinate { 169 | x: number; 170 | y: number; 171 | } 172 | ``` 173 | 174 | New interface extending others and adding additional properties 175 | 176 | ```ts 177 | interface Field extends Base, Coordinate { 178 | name: string; 179 | } 180 | const myField: Field = { 181 | length: 100, 182 | x: 10, 183 | y: 20, 184 | name: "My stuff", 185 | }; 186 | ``` 187 | 188 | New interface extending others but without any additional properties 189 | 190 | ```ts 191 | interface NoNameField extends Base, Coordinate {} 192 | const somebodyField: NoNameField = { 193 | length: 100, 194 | x: 10, 195 | y: 20, 196 | }; 197 | ``` 198 | 199 | Could be also defined by `type` 200 | 201 | ```ts 202 | type NoNameFieldType = Base & Coordinate; 203 | ``` 204 | 205 | Interface could force readonly properties 206 | 207 | ```ts 208 | interface TryToAssign { 209 | a?: number; 210 | readonly b: string; 211 | } 212 | const readOnlyObject: TryToAssign = { b: "b" }; 213 | readOnlyObject.a = 10; 214 | ``` 215 | 216 | This will not work: `readOnlyObject.b = "10"` 217 | -------------------------------------------------------------------------------- /docs/pages/language/utility-types.md: -------------------------------------------------------------------------------- 1 | # Utility Types 2 | 3 | There are utility types provided by typescript which are useful for common use cases 4 | 5 | ## Partial 6 | 7 | Allows to make all object properties "optional" 8 | 9 | ```ts 10 | interface FullType { 11 | first: string; 12 | last: string; 13 | } 14 | const fullData: FullType = { 15 | first: "a", 16 | last: "b", 17 | }; 18 | const partialData: Partial = { 19 | last: "test", 20 | }; 21 | console.log(partialData); 22 | ``` 23 | 24 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIFcA2mAqBPABxQG8AoZZGYKAZzAC5k6pQBzAbnOUzjsebacAvqQQB7EHUpZMAEThg4jDNnxFkAXmRkKVWg2QAiOIYA0XHnyMAjM6SGdxksMgJwoYYHDkKlyAArunt4APCq4hBAAfJraFrwGhpB0dg6iEjRimBAAdJhirAAUbh5ePooAlOxAA) 25 | 26 | Commonly used for partial data merge functions 27 | 28 | ```ts 29 | interface FullUserMerge { 30 | first: string; 31 | last: string; 32 | email: string; 33 | gender: string; 34 | } 35 | const fullUser: FullUserMerge = { 36 | first: "a", 37 | last: "b", 38 | email: "test-email", 39 | gender: "f", 40 | }; 41 | const mergeDataIntoUser = ( 42 | user: FullUserMerge, 43 | partialData: Partial 44 | ): FullUserMerge => ({ 45 | ...user, 46 | ...partialData, 47 | }); 48 | 49 | const mergedData1 = mergeDataIntoUser(fullUser, { gender: "f" }); 50 | const mergedData2 = mergeDataIntoUser(fullUser, { 51 | email: "some-email", 52 | last: "x", 53 | }); 54 | console.log(mergedData1); 55 | console.log(mergedData2); 56 | ``` 57 | 58 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIFcA2mCqBnaAWWgHMUBvAKGWRmCjzAC5lGpQSBua5TORlmw7caEALZxgmQWHYguPMiAAm0GXIUBfSggD2IRrSy4CUFhmz4ipFAF5kVGnQbNkAIjhuANDz4D3AEbePOKS0u6QjAC0oVLBNEqqZu4wwZrcegZgyGI2ACJwYHAAkuC6VlDI9gAUPOim5sYVxFBkPjQADnBQYMBwmAVFLAAK3b39ADwWJtatEAB8lACUjZamLWRV88jVjsgAdIf10O0Hh109fQOFcD6aS9w6+oa5c8qDcACMVTn5N6VgcqmaowJqmLwOZCJNQpNzIe4ZZ7ZV5kd43ABMPxREA+AKB0BBYJODhCEikLDceF0uRiZMw8V4-FcbgAHmkHk8DLpMBB9phdCRqti0UVPhzMlSeXyBUKbCK4OiOUA) 59 | 60 | ## Required 61 | 62 | Opposite of `Partial`, makes all properties required 63 | 64 | ```ts 65 | interface MaybeSomeOptions { 66 | db?: string; 67 | apiUrl?: string; 68 | api: string; // this is not required, it will stay as is 69 | } 70 | type AllRequiredOptions = Required; 71 | const allRequiredOptions: AllRequiredOptions = { 72 | db: "db-name", // required now 73 | apiUrl: "google.com", // required now 74 | api: "api-url", // required now 75 | }; 76 | console.log(allRequiredOptions); 77 | ``` 78 | 79 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgLJwJ4CMIGUD2AthAPIAOYw+IAzsgN4BQyyAJlgPwBcyNYUoAOYBuZsjhlgAVSgAbbr35DRLCcB58BIEcgD0u5GAAWwOqeQh8YZFAgBHAK7BbrADTJg1gO7BZsxZjiZjSMAL6MYBhkKACCfgBK9k4u5JTUdAC8yImOzhCsADzo2HhEpBRUtAB8ogjp1nAJSXmsqZU0PHGyOcn5benIWUws7DwAROwAtCBwxGPu+jbNLhb4XmJqMrLjgvj4grIQAHR1hPN6Bra5K5brqpLjapMOcueLV72sq+uhten4hyOsn2AApGt1ln0KukAJSiIA) 80 | 81 | ## Record 82 | 83 | Allows to build an object type using 2 types: one for key and second for value 84 | 85 | ```ts 86 | type FixedMap = Record<"a" | "b", number>; 87 | const fixedTypeInstance: FixedMap = { 88 | a: 1, 89 | b: 2, 90 | // c: 3,// ... and 'c' does not exist in type 91 | }; 92 | ``` 93 | 94 | These 2 definitions are equal: 95 | 96 | ```ts 97 | type ObjectWithStringKeys = { [key: string]: any }; 98 | type RecordWithStringKeys = Record; 99 | ``` 100 | 101 | This type is very useful when object shape is not fully known. 102 | 103 | Commonly used as a type for json parsing results, 104 | json should have keys as a `string` but contain many various "values" 105 | 106 | ```ts 107 | const jsonData: Record = JSON.parse(`{"nobody":"knows"}`); 108 | ``` 109 | 110 | Any type could be used to represent value 111 | 112 | ```ts 113 | interface DynamoDbRecordType { 114 | userId: string; 115 | data: number; 116 | } 117 | const recordsByUserId: Record = { 118 | "1": { 119 | userId: "1", 120 | data: 100, 121 | }, 122 | "2": { 123 | userId: "2", 124 | data: 200, 125 | }, 126 | }; 127 | console.log(recordsByUserId); 128 | ``` 129 | 130 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCIE8RwLYHtUBGAShArlACYAq6ADigN4BQyyArgM7QCSFAXMg5gooAOYBuFsgpwwcASDbYC0SQF8mZEEORRS5ChwBC6AKpcovASTKUAPEJEhRAGjSYc+Yvso16APmQAXmRmVgAiAEZwgTDWdgsrZCjwlylWGTkBSIAGHLTWNQLkgCYY0PSEnn5S1MrM+WQSvOKipjVJLQ5cABsIADoe3FEACj1bQxNzaoBKSSA) 131 | 132 | ## Readonly 133 | 134 | Set all properties of an object as `readonly` 135 | 136 | ```ts 137 | interface DynamicInterface { 138 | a: string; 139 | b: number; 140 | } 141 | type ReadonlyInterface = Readonly; 142 | const readOnly: ReadonlyInterface = { 143 | a: "x", 144 | b: 1, 145 | }; 146 | console.log(readOnly); 147 | ``` 148 | 149 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCIE8RwLbAQSXGniWQG8AoZZOALmQGcwpQBzAbiuQCN6QBXbN2icAvhTDoADigBKEOABMA9iAA26QpFiIUAXmTylqjQB4MWXASI6kAPk4JVTZFAWKA8uvT0jK71rEusgGlNR0yABEAB6RADRcvMgAjAmijs7KahAAdGrKrAAUbkpeGgCU7EA) 150 | 151 | readOnly.a = "xx"; // Cannot assign to 'a' because it is a read-only property. 152 | 153 | ## Pick 154 | 155 | Constructs new type by "picking" properties from another type. 156 | 157 | In practice not so often used. 158 | 159 | ```ts 160 | interface FullBigType { 161 | one: number; 162 | two: number; 163 | three: number; 164 | four: number; 165 | five: number; 166 | } 167 | type OneAndTwo = Pick; 168 | const oneOrTwo: OneAndTwo = { 169 | one: 1, 170 | two: 2, 171 | // three is not here 172 | }; 173 | console.log(oneOrTwo); 174 | ``` 175 | 176 | [open code in online editor](https://www.typescriptlang.org/play?#code/FASwdgLgpgTgZgQwMZQAQDECuAbbAhEAcwBUBPABzQG9hVUB7MKALlTEwFsAjWAbltQQA7vVbtufARAAWMKCzaceMfnTj1MMMUslqQANwXjl-AL7AIFNAHkmAQTAATYiNQBeVAAUQSANYAeLFwCEisAGlQAIkYoSNQAHyjhekiAPn4kRgBnCAYmaxgXUVRbKAdnVw8aOhjWAEYwqRFWACZGugB6DsFZeToQLLZ6XOlYKGBTDOz6bCgAOmx6QgAKGIKigEpeIA) 177 | 178 | ## Omit 179 | 180 | Removes some properties from type definition 181 | 182 | ```ts 183 | interface DatabaseUserSecret { 184 | userId: string; 185 | login: string; 186 | password: string; 187 | name: string; 188 | } 189 | type DatabaseUserPublic = Omit; 190 | const privateDbUser: DatabaseUserSecret = { 191 | userId: "1", 192 | login: "username", 193 | password: "secret", 194 | name: "My Name", 195 | }; 196 | const publicDbUser: DatabaseUserPublic = { 197 | userId: "1", 198 | name: "My Name", 199 | // login: "username", // ...and 'login' does not exist in type 200 | // password: "secret", // ...and 'password' does not exist in type 201 | }; 202 | console.log(privateDbUser); 203 | console.log(publicDbUser); 204 | ``` 205 | 206 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCJzHARnAzhAVXygGUIEoIxkBvAKGWQFdiBJAEwC5lcwpQA5gG4GyADYB7AaG69+IYaIAOeXAHcJULjz6CRjEHAC2EWboUiAvnTABPJSnSYc+ItAAKTLGOAJkAXmQAeSNgMAAeJ2w8QmIyCioAGmQAIklpEBTkAB9UlVx1TXYUgD4RBAkQXmQlfgA3DAhULDcobiiXWOh4ympA+kYWaA5uFIBGFMTRdJlUoahDE0nlVQ0tUfwEsGWDY1NUgFlbZAA5PeXLcsrqpS8fBGbW9oxo12JPb18A2lF5kdSJlNdiZRkdTucgcgAPRQ8RSWYpeaLCCTaGwgB0mLgIHYyAA5DMQHjkOwJBBcMgQBJqBAAB7AaqgZB2ByiGE1VZFDbkXqo9mY9HY3F4-KFLTE0nkynU5B0hnUJksiB0S50CpVCRiCDo9IAClqwAakEexAAlFcNVqdVJ9XdfCboOagA) 207 | 208 | This type is useful for a cases when type of some properties should be changed 209 | 210 | ```ts 211 | interface DynamodbItemPlain { 212 | recordId: string; 213 | createdAt: string; 214 | updatedAt: string; 215 | data: string; 216 | } 217 | 218 | interface DynamodbItemData { 219 | userId: string; 220 | name: string; 221 | } 222 | interface DynamodbItemParsed extends Omit { 223 | data: DynamodbItemData; 224 | } 225 | const plainDbItem: DynamodbItemPlain = { 226 | recordId: "record-id", 227 | createdAt: "2020-01-01", 228 | updatedAt: "2020-02-02", 229 | data: `{"userId":"user-id", "name":"User Name"}`, 230 | }; 231 | const convert = (origin: DynamodbItemPlain): DynamodbItemParsed => ({ 232 | ...origin, 233 | data: JSON.parse(origin.data), 234 | }); 235 | const parsedDbData = convert(plainDbItem); 236 | console.log(parsedDbData); // this will have data as `DynamodbItemData` 237 | ``` 238 | 239 | [open code in online editor](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgCIE8RwLYHsAmARgJKTYAKANnKMgN4BQyyUECuU+x+AXMgM5gooAOYBuJsgSs4kfAEEwfQcJDjJAVwAO+WRAVKBQ0ROa6wcZcbUSAvgwahIsRCgxY8RUhGypZcek1+aG4rVXVmDwgwkwZ7J2h4JDRMHAISMnI4KGD8ZAgAD0gQfH5kAHlsYDAAHnc0r0zqUAAaZAAiczh2gD5As38+es8Mnz8LOwZ2EEFkLWaQVFHsIdSR7woF5ABefpY2Di5eDtZ2TgBaYHx2lslpCD0DPnaAJgAGd-O3gEYv75vNDpHopnu9Ph8vi8AQMLHwAAZ0doaYJQbjtHhIlGXa5tdpRdHtACqKOQADkcBB2rY4bdbBJprNpgA3aBgHbIAAUHGAIlAqw86Q2VBoIAAlPyGsssjl9Ds+hzGMwAHQq7m8kC3GGWZAAKQAyuVSUqtNlglzhOqlV1RbTRfTcDM2SaZfgluMArtmayOfMRUsNnapg7+LhKBAlZRcCIfab9G7-HbkAB6JPIMAAC2AZQA7sBKJRkOm4CzkF1kHAynDhoKyO64QwgA) 240 | 241 | ## Other types 242 | 243 | Check more utility types at official [docs page](https://www.typescriptlang.org/docs/handbook/utility-types.html) 244 | 245 | `Exclude`, `Extract`, `NonNullable`, `ReturnType` etc are not so often used 246 | -------------------------------------------------------------------------------- /docs/pages/language/variables.md: -------------------------------------------------------------------------------- 1 | # Variable definitions 2 | 3 | Classical `var` is working to be compatible with javascript 4 | 5 | ```ts 6 | var simpleVar = 1; 7 | ``` 8 | 9 | It has problems with scoping, so it is usually forbidden to use by tsc options 10 | 11 | Recommended to use: 12 | 13 | - `let` for variables changing values 14 | - `const` for constants having one value all time. 15 | 16 | ```ts 17 | const databaseAdapter = {}; 18 | let index = 0; 19 | ``` 20 | 21 | You could still modify internal properties of objects and arrays defined with `const`: 22 | 23 | ```ts 24 | const object = { 25 | a: 1, 26 | b: 2, 27 | }; 28 | object.a = 10; 29 | ``` 30 | 31 | object = {a: 10, b:2 } // will not work 32 | 33 | ```ts 34 | const myArray = []; 35 | myArray.push(10); // ok 36 | ``` 37 | 38 | Object key short notation 39 | 40 | ```ts 41 | const userId = 10; 42 | const userObject = { 43 | userId, // this mean: take `userId` as property name and assign value from `userId` variable 44 | }; 45 | ``` 46 | 47 | # Block-scoping 48 | 49 | ```ts 50 | function checkForError(isError: boolean) { 51 | let errorPrefix = "Error: "; 52 | if (isError) { 53 | let fullError = errorPrefix + "here"; 54 | return fullError; 55 | } 56 | // @ts-ignore 57 | return fullError; // Error: 'fullError' doesn't exist here 58 | } 59 | ``` 60 | 61 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABBAFgUwgawGJwE4Ciee+AFDAM5El4BciARnHADZoCGYAlIgN4BQiRGyiI0xfAAU8aYDAAeiALyIARNXz1VAbkGIYwROSoS8PAUKEjEoFiw15lY09NkLEAajXoZOvUJkoEDwkW3tTXSEAXz0AeljEAAEoCgBaGABzMHw0PUDg0JA7B21EeMQHegByMIcqxAATODQKMCrRNHlKUR9cmKA) 62 | 63 | `let` works within scope and could be shadowed within child scope 64 | 65 | ```ts 66 | function checkForErrorAnother(isError: boolean) { 67 | const errorPrefix = "Error: "; 68 | if (isError) { 69 | const errorPrefix = "Internal Error: "; 70 | let fullError = errorPrefix + "here"; 71 | return fullError; 72 | } 73 | return errorPrefix + " not happened"; 74 | } 75 | const result1 = checkForErrorAnother(true); // "Internal Error: here" 76 | const result2 = checkForErrorAnother(false); // "Error: not happened" 77 | console.log(result1); 78 | console.log(result2); 79 | ``` 80 | 81 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABBAFgUwgawGJwE4Ciee+AgmHFOngBQwDORJeAXIgEZxwA2aAhmACUiAN4AoRMgT0oiNMXwAFPGmAwAHogC8iAERN8bXQG4JiGMER1GCvMPGTJEabPnNlqjdr0BJMFHkwPm5EA1Y9U0dEXllQbm4w7zclFTVNAGo9ajQTM0kVKBA8JDiE20jEAF8zAqKkZLwPNMRM3UQKWRQ+AAdutDA0ABNc6ucwGUQVehBuKABGb1QMHHww8kpqGig8EDRBY0QAekPff0Dg0Ns2bN0xMYmpmagAJkX0LFxCW3WqeRpgYL0PYHY56MJsdqURBdXr9Ia3e48NAAOm4cAA5jRHrM5vs7tIkaiMVi0NNZs88UA) 82 | -------------------------------------------------------------------------------- /docs/pages/topics/array-async.md: -------------------------------------------------------------------------------- 1 | # Async and arrays 2 | 3 | ## Sequential for 4 | 5 | Use `for` for sequential async function execution 6 | 7 | ```ts 8 | const sequentialAsync = async () => { 9 | const data = [5, 7, 3, 10, 40]; 10 | 11 | const apiCall = (ms: number): Promise => 12 | new Promise((res) => { 13 | console.log(`-> ${ms}`); 14 | setTimeout(() => { 15 | console.log(`<- ${ms}`); 16 | res(ms * 100); 17 | }, ms); 18 | }); 19 | 20 | const asyncRes = []; 21 | for (const i of data) { 22 | asyncRes.push(await apiCall(i)); 23 | } 24 | console.log(asyncRes); 25 | }; 26 | sequentialAsync(); 27 | ``` 28 | 29 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBBCmBHArvMUCWBDANgQQgE8xgYBeGLIkmACgEpyA+GAbwCgYZRJYATLFCzkYAbQCsAGhgB2aQGZpARgAM0gCwqAugG52nbuGiUADhgDCuHCNoBbCAC4YYZLYBG8AE70nABU8gthgIADwu7l4sZEwGXGDwAO4w-oHB8LS0nvAQjNFssVyGkCA48AB0OCAA5rQABgC0LAAkrPYAvrX0eoWFCFAAKhi28CDIUBm5LBw9PTwQJeWVNbUh9TAt7Z3dM1xZEHYQMABUMKoqXQVcbdL2Fz1tFwZzsFTEwABK2SKiugYAZiBPHRnjAMDAQH8YAIhIxpoVXiRPhAyiZkBAABa0LAJLAYF5mSw4HC0DD0O4wNpPIwLCrVLHUD7ZC5tPQIFBoTC4AhvBg6IA) 30 | 31 | Expected output: 32 | 33 | ``` 34 | -> 5 35 | <- 5 36 | -> 7 37 | <- 7 38 | -> 3 39 | <- 3 40 | -> 10 41 | <- 10 42 | -> 40 43 | <- 40 44 | [ 500, 700, 300, 1000, 4000 ] 45 | ``` 46 | 47 | ## Parallel async map 48 | 49 | Using `.map` for parallel async functions execution 50 | 51 | > Remember: First rejection will throw an error in function without waiting for others to finish. 52 | 53 | This behavior usually is what is expected: Fails as soon as any request fails. 54 | 55 | ```ts 56 | const parallelAsyncMap = async () => { 57 | const data = [5, 7, 3, 10, 40]; 58 | 59 | const apiCall = (ms: number): Promise => 60 | new Promise((res) => { 61 | console.log(`-> ${ms}`); 62 | setTimeout(() => { 63 | console.log(`<- ${ms}`); 64 | res(ms * 100); 65 | }, ms); 66 | }); 67 | 68 | const asyncRes = await Promise.all( 69 | data.map(async (i) => { 70 | return await apiCall(i); 71 | }) 72 | ); 73 | console.log(asyncRes); 74 | }; 75 | parallelAsyncMap(); 76 | ``` 77 | 78 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBADgQwE4IDaoKaoIIQJ5jACyCcMAvDAvoTABQCUFAfDAN4BQMMoksAJgigIKMANoBWADQwA7DIDMMgIwAGGQBZVAXQDcHLj3DQqcAJYBhNKlF0AthABcMMAFc7AIwxIGzgApIIHZmEBgAPG6e3qzkzIbcYBgA7jABQSEYdHRIGBBMsezx3EaQIJgAdKggAOZ0AAYAtKwAJGwOAL51DPrFxaFQACpmdhggrlBZ+aycvb28EGUYlTX1YQ0wrR1dPbPcORD2EDAAVDBqqt1F3O0yDpe97ZeG87DUBMAASrmiCEkIZrA0sFQuVrHQioJhOU7KQ6G9aHQzFNCrsclBXEgwFQ-gDTJYwUidtcGIZ7iUFhUqrV4Z9cpd2vpECh0FhcO8SHBGLogA) 79 | 80 | Pay attention that `<-` are happening in parallel and returning in order (low to high). 81 | But overall results array is built from corresponding results (not ordered) 82 | 83 | ``` 84 | -> 5 85 | -> 7 86 | -> 3 87 | -> 10 88 | -> 40 89 | <- 3 90 | <- 5 91 | <- 7 92 | <- 10 93 | <- 40 94 | [ 500, 700, 300, 1000, 4000 ] 95 | ``` 96 | 97 | ## Async reduce 98 | 99 | Using async function in reduce. 100 | 101 | ```ts 102 | const sequentialData = [5, 10, 15, 95, 150]; 103 | 104 | async function sequentialAsyncReduce() { 105 | const apiCall = (ms: number): Promise => 106 | new Promise((res) => { 107 | console.log(`-> ${ms}`); 108 | setTimeout(() => { 109 | console.log(`<- ${ms}`); 110 | res(ms * 100); 111 | }, ms); 112 | }); 113 | 114 | const results = await sequentialData.reduce( 115 | async (acc: Promise, val: number) => { 116 | const results = await acc; 117 | await apiCall(val); 118 | return [...results, val]; 119 | }, 120 | Promise.resolve([]) 121 | ); 122 | console.log(`Results ${results}`); 123 | } 124 | sequentialAsyncReduce(); 125 | ``` 126 | 127 | [open code in online editor](https://www.typescriptlang.org/play?#code/FAYw9gdgzgLgBFApgRwK6IjAlgQwDYAiOMOcAvHANoCsANHAIwAM9DdcAnO20wLoDcwYDigBPCCDgAzVBOyQEKdJlx4AgmIkAlRABNUIRAAoAlHADewOHHDR4OAA5YAwvjzk4RgLZQAXHAhULwAjRAAnE38ABTCwLywkAB5AkPCAPnI0q2sAxAB3OBi4hOMjMMQoMzIMyxyc2ygwPEQAOjwwAHMjAAMAWgyAEnMfAF9uk0E66yQYABUsL0QwVBgjU0yLbKmbSEbmts6exN64IdHxye24cqhvKDgAKkYmJgmtnJH6Hze6kbfshrwG6oPAwe4UHB5HBYeBINAYbD4IgkFrlfSGIzvETiSRGHAgEDRWLxJIpUJhSi8NL0ABu+H8ZPCVRq72sgOuFRBYI8kOh9gJlzqvJhcEcLjcRjpeB+U3KMFQYQgVBaKuBoKgtPwAnen3eRRJrRuTRpxkpJmyMoaTVa7S63R0UC59yGarBYzeI2AcOUiPUmhAOnRxgmQA) 128 | 129 | Expected output: 130 | 131 | ``` 132 | -> 5 133 | <- 5 134 | -> 10 135 | <- 10 136 | -> 15 137 | <- 15 138 | -> 95 139 | <- 95 140 | -> 150 141 | <- 150 142 | Results 5,10,15,95,150 143 | ``` 144 | 145 | Use `.map` and `.reduce` to apply async function over array elements in groups. 146 | 147 | ```ts 148 | const chunk = (input: number[], count = 10): number[][] => 149 | input.length > 0 150 | ? [input.slice(0, count), ...chunk(input.slice(count), count)] 151 | : []; 152 | 153 | async function parallelAsyncReduce() { 154 | const data = [5, 10, 15, 20, 25, 40, 50, 100, 120, 150, 100, 90, 95, 150]; 155 | 156 | const apiCall = (ms: number): Promise => 157 | new Promise((res) => { 158 | console.log(`-> ${ms}`); 159 | setTimeout(() => { 160 | console.log(`<- ${ms}`); 161 | res(ms * 100); 162 | }, ms); 163 | }); 164 | 165 | const mapChunks = ( 166 | localData: number[], 167 | cb: (i: number) => Promise, 168 | size = 5 169 | ): Promise => { 170 | const batches = chunk(localData, size); 171 | return batches.reduce(async (acc: Promise, batch: number[]) => { 172 | const arr = await acc; 173 | const thisBatch = await Promise.all(batch.map(cb)); 174 | return [...arr, ...thisBatch]; 175 | }, Promise.resolve([])); 176 | }; 177 | 178 | const result = await mapChunks(data, async (value: number) => { 179 | return await apiCall(value); 180 | }); 181 | console.log({ result }); 182 | } 183 | parallelAsyncReduce(); 184 | ``` 185 | 186 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBMAWBXMBrGBeGAKAlmADolAFwxiIC2ARgKYBOA2gLoA0cIysmAjAAwCUpctXrNmGAHwAoGDDyEoAOgA2NMAHMo8GBJi8ZsmAH4YDeUUURlOYDSy82oTvzaK3CZClwELVm3acwKBd2ZyYDWVJmAG4pKQBDCABPMGAYADNkYCgccBh8eLp45VVlAEFk1IAlGgATRFssfhgAbwNQSFha+Kh4jFMAVjY+YaGYACYHCbGAFimBqb5FydHF3imATk2x7gWmWPbwaBh4-BwAYWLlfqwKCCFKWjpBGAAFOhAKHAgaAB5hJ66dDSQxkGgAdzeHy+PywWDoNAgzWBrQihg6EBAqhUIHUWAABgBaXQAEhadwAvvj+LFQbIflAACo4Cg0DhQOHI3RtOnoo5Ymg4vH436EmBkynU2m8mAIiC3CAwABUMCWNLRsgpbDu6tBFPVh06MAop3OSFQiswWDRyhAwGKABEevEHiJGKw0cAqKRcK6nlyoZ9vn8AfQJCw0RAcAAvGj9AYGF7vIM-f6PURMIHcz1HWBUHoIRH9DyoLC2+3KJ29NhR2O6wwIqCIOhgGD5qCFiCKBH1RqJFJpLDxYDAUjJmEh9Pu8NtgvwP0ZgM8ukY2CFOj9eLg+I4Ncj6Wg1cwLTfABCc83293gYniiuWHbCEUJvwWC9-HroMbzdbDDcd7oOhXDcE8IHPDt4H2NEtRvYNu0RLEADc7GYD9pQpA5ZCPOVEGULgTivWAXzNTx5W6asTkqQdEOKRAaAXZ5JFRL8aCbFsCJ3NczkuEosBo5Q6PrfVpQxAUhSwFpZURXDYGEqQKSkAoihKGhyiompezsdUgA) 187 | 188 | Expected output: 189 | 190 | ``` 191 | -> 5 192 | -> 10 193 | -> 15 194 | -> 20 195 | -> 25 196 | <- 5 197 | <- 10 198 | <- 15 199 | <- 20 200 | <- 25 201 | 202 | -> 40 203 | -> 50 204 | -> 100 205 | -> 120 206 | -> 150 207 | <- 40 208 | <- 50 209 | <- 100 210 | <- 120 211 | <- 150 212 | 213 | -> 100 214 | -> 90 215 | -> 95 216 | -> 150 217 | <- 90 218 | <- 95 219 | <- 100 220 | <- 150 221 | { 222 | result: [ 223 | 500, 1000, 1500, 224 | 2000, 2500, 4000, 225 | 5000, 10000, 12000, 226 | 15000, 10000, 9000, 227 | 9500, 15000 228 | ] 229 | } 230 | ``` 231 | -------------------------------------------------------------------------------- /docs/pages/topics/errors.md: -------------------------------------------------------------------------------- 1 | ## Errors 2 | 3 | There are multiple strategies how to handle an error. 4 | 5 | It is always performed by `try-catch` statement. 6 | 7 | > Remember: Async function invoking will throw rejected value. It can be captured by try-catch 8 | 9 | [Read more at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) 10 | 11 | With a recent introduction of `unknown` type in a 'catch' block, value whould be first checked to be an error: `if (e instanceof Error) { ... }` 12 | 13 | ### Catch all 14 | 15 | ```ts 16 | function throwError() { 17 | try { 18 | throw new Error("My Error"); 19 | } catch (e: unknown) { 20 | console.log(e); 21 | } 22 | } 23 | throwError(); 24 | ``` 25 | 26 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABFAFgJzgdwKJo2gCgEpEBvAKEWTQE8zKrl0tEwBTTRXfAgIgFk63OGl5EA3AwC+iCAEMoEFIgJsAXInABrMFjAkKjWQgDOcADZsAdObgBzVROnkp5VBhx4RxcUA) 27 | 28 | ### Re-throwing errors 29 | 30 | In some cases you can expect some error, perform an action and throw the same error to be processed 31 | on higher levels. 32 | 33 | ```ts 34 | function reThrow() { 35 | try { 36 | throw new Error("My Error"); 37 | } catch (e: unknown) { 38 | console.log(`Error happened: ${e}`); 39 | throw e; 40 | } 41 | } 42 | reThrow(); 43 | ``` 44 | 45 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAJwKYBUAWy4HcAUAlIgN4BQiiUyAnqRZVdnomKrogKLI7L4BEAWTrde-QgG4GAX0QQAhlAiZE+VAC5E4ANZg8YYuUZyEAZzgAbVADoLcAOb4ABqLjJEmeQAcvqNgBNNABISVGknSQZKKGYOVClKaTIktCwcAkkgA) 46 | 47 | ### Catching only subset 48 | 49 | You can filter out only necessary errors in `catch` using `instanceof` 50 | 51 | ```ts 52 | function filterErrorInstanceOf() { 53 | try { 54 | throw new TypeError("Bad Type"); 55 | } catch (e) { 56 | if (e instanceof TypeError) { 57 | console.log(`Expected type error: ${e}`); 58 | } else if (e instanceof RangeError) { 59 | console.log(`Expected range error: ${e}`); 60 | } else { 61 | // re-throw all others 62 | throw e; 63 | } 64 | } 65 | } 66 | filterErrorInstanceOf(); 67 | ``` 68 | 69 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMGAbKBTATgUS1uLASTAGcoBDSDAeWAAoBKRAbwChFEosBPVjzlwAWBAO6IwGcQBUeABwx4CWegCIAQhQAmiWQtWMA3AIC+iCBSgQhiehmbtBiGMFsZnZStTiu9i-IQOAk4QCKRwqBgAdKhwAOb0AAY4AB4K0Bg6UPLu2MoAXIgAJCwYJolGwYhmGKik7i5uHuRUEBg+iABKVHH+ykFOnKFkEdGxCclpGBk6WD25AViFJWUVxk41de6OgwD0u4hYGAC0UCJw4hSoqIhwZ9ikVZxnYogY64ImpmxfKOjYSkIJBa1DoTGMQA) 70 | 71 | Or as variation use `switch` case: 72 | 73 | ```ts 74 | function filterErrorSwitch() { 75 | try { 76 | throw new TypeError("Bad Type"); 77 | } catch (e: unknown) { 78 | if (e instanceof Error) { 79 | switch (e.constructor) { 80 | case TypeError: 81 | case RangeError: 82 | console.log(`Expected error: ${e}`); 83 | break; 84 | default: 85 | throw e; 86 | } 87 | } 88 | } 89 | } 90 | filterErrorSwitch(); 91 | ``` 92 | 93 | [open code in online editor](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMGAbKBTATgUS1uLAZQHcYoIALACgEpEBvAKEUSiwE9GXW3KCSiMBkEAVDgAcMeAlmoAiAEIBDACaJxU+bQDcPAL6IIyipUTUMALkTgA1mDgkw9Zr0Qxg5jO7ABnKMqQGHCeMoQuPG6+ZKZeAHQQCP5YINDh3G5uxr7emtL4hJaRmdneAEqBAOb5skWZJUlwqBhxqHCV1AAGOAAeUtAY6ti1iAAkDBj6nbrF9QBGWBjKtnr1rKoYwMog6HVrrFD8jogYq5n6xResFxco6NhhxDFUdHpAA) 94 | 95 | ### Use finally 96 | 97 | `finally` is called each time after `try` or `catch` been called, regardless of thrown exceptions 98 | 99 | ```ts 100 | function errorFinally() { 101 | try { 102 | throw new TypeError("Bad Type"); 103 | } catch (e) { 104 | if (e instanceof TypeError) { 105 | console.log(`Expected type error: ${e}`); 106 | throw e; 107 | } 108 | } finally { 109 | console.log("I will be called always"); 110 | } 111 | } 112 | errorFinally(); 113 | ``` 114 | 115 | ### Custom Errors 116 | 117 | Custom errors can be thrown as well. To be able to do it, just extend `Error` class, and throw newly created instance. 118 | 119 | > Remember: Do not `throw` anything except Error or it's subclasses 120 | 121 | ```ts 122 | class InvalidInputError extends Error { 123 | constructor(message: string) { 124 | super(message); 125 | this.name = "InvalidInputError"; 126 | this.stack = `${this.message}\n${new Error().stack}`; 127 | } 128 | } 129 | const invalidValue = 100; 130 | try { 131 | if (invalidValue > 0) { 132 | throw new InvalidInputError("Some field is not valid"); 133 | } 134 | } catch (e: unknown) { 135 | if (e instanceof Error) { 136 | console.log(e.name); // InvalidInputError 137 | console.log(e.message); // Some field is not valid 138 | console.log(e.stack); 139 | // Some field is not valid 140 | // Error 141 | // at new InvalidInputError (...) 142 | // at eval (...) 143 | console.log(`Error happened: ${e}`); // Error happened: InvalidInputError: Some field is not valid 144 | } 145 | } 146 | ``` 147 | 148 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYGwhgzhAECSB2A3MICWATBAHArgFwFEAnIgeyOgFMAPPS+dGYsigbwChpphT4I8iOYHnIAKALaUoYAOaUAXNH5FU8GQEpoHLlwg4slIhKkRZldQG5OOvAAtUEAHTwwk6AF5oAIgTI0meFxCEnIvKx1oOwdHfjBgAGsPaAADABJWKKdJaTkAXwAdeHT4SgB3aGYxdRi8OPjc5PDoXPYWnj48aFU-DAA1FBxKJIBGAAZRqwEATy1rVAAzaFFulD6BoYA+aFHNbRtbMnKS8t9VgKDKoy8AZVI3edRKEHQumHhSTp70L0trFtzuGA8MBbEsFNAcPB4u9SvBdnNFqIhqpYvBgJRSItLvCIu0IKQQJRHCBSDIkc5XOYLNAAPQ0uBIM7YfCXaxcPEEokkslE7KmOSWWn0273R7PV7Qd6fM5s7i8fGE4mk8mxBK-CJ06AioYPJ4vBySj7QL6yzWsjX0nRAyVlBlfZnBFhLRwu9Smy1ca2UPzO12yjmK7miZKXaC2MBYAwldCKdKUBqCs0hCjhyP0Sgxu1MwIs5OKbXQXXig1S40yrj-dhAA) 149 | -------------------------------------------------------------------------------- /docs/pages/topics/replace-class.md: -------------------------------------------------------------------------------- 1 | # Use interface instead of class 2 | 3 | `class` definition can be replaced by function returning required object interface instance `DbAdapter`. 4 | This is more popular approach compared to define new `class` and using `new` 5 | 6 | > Always try to use functional style ! 7 | 8 | Sample database adapter with few methods and single property 9 | 10 | ```ts 11 | interface DbAdapter { 12 | databaseName: string; 13 | put: (item: string) => void; 14 | get: (id: string) => string | undefined; 15 | } 16 | ``` 17 | 18 | Could be created inside of `buildDatabaseAdapter` which receive "constructor" parameter `databaseName` 19 | Both functions `get` and `put` are exposed by interface definition as public. 20 | 21 | Within function you could define any other scoped functions, they will remain private. 22 | 23 | ```ts 24 | const buildDatabaseAdapter = (databaseName: string): DbAdapter => { 25 | const realDatabaseConnector = {} as any; 26 | 27 | // private 28 | const _getData = (id: string) => realDatabaseConnector.get(id); 29 | 30 | // exposed via return, it is public 31 | function get(id: string) { 32 | return _getData(id); 33 | } 34 | function put(item: string) { 35 | // no `this` required here 36 | return realDatabaseConnector.put(item); 37 | } 38 | 39 | return { 40 | databaseName, 41 | put, 42 | get, 43 | }; 44 | }; 45 | const myDbAdapter: DbAdapter = buildDatabaseAdapter("sample"); 46 | ``` 47 | 48 | Notable advantages: 49 | 50 | - No need to use `new` to initialize instance of the class, just function call 51 | - No need in constructor and reassigning parameters to class fields 52 | - `this` usage issues disappear, all functions have access to scope variables 53 | -------------------------------------------------------------------------------- /docs/pages/topics/union-enum.md: -------------------------------------------------------------------------------- 1 | # Using Enums 2 | 3 | When your emun is defined just to be supplied as parameter into some other functions. Continue using it. 4 | 5 | ```ts 6 | enum MyEnumUserType { 7 | ADMIN, 8 | UNKNOWN, 9 | REGISTERED, 10 | } 11 | const processUserData = (data: any, userType: MyEnumUserType) => { 12 | if (userType === MyEnumUserType.ADMIN) { 13 | console.log(`Admin data:`, data); 14 | } else if (userType == MyEnumUserType.REGISTERED) { 15 | console.log(`Registered user data:`, data); 16 | } else { 17 | console.log(`Unknown data:`, data); 18 | } 19 | }; 20 | processUserData({ test: 1 }, MyEnumUserType.ADMIN); 21 | processUserData({ test: 2 }, MyEnumUserType.UNKNOWN); 22 | ``` 23 | 24 | [open code in online editor](https://www.typescriptlang.org/play?#code/KYOwrgtgBAsgngUXBAqgZ2AJwCpwA7BQDeAUFFAIIAiMAkgHIA0ZUK9A0vQPIDqTLAJQQBxWgGVsCIVWYBfEgGMA9iDQAXKHkxKFwNGnRYqAQzXGoAXigAKACanjALijGQcRlDAYc+YM-hIkIY+BACUlgB8xCwAlgBmNl5YuASWFlYByMEpwAB01HT04aTk5MqqSgA2eZVKAObWAAYUthAxIFD2Zo6NHl3GoQDcLLJQwJUYUPGJ3jlpsIhZs765QqISUghUxSxlKmhVNfVNAsB1MepYwLae3p0OPX0OQyNjE4QlpeUH1bm1DY0UCAANYgJQAdw6-Ue9zML3I8lkwy0Oj0Bm8JjM1iIUDUejUzgAjFBZB5MkFlgR8jQGC8Ubp9MFMcZsbj8c4AEwksmLCnJFZsTi8IqDIA) 25 | 26 | In such situations it is usually created (hardcoded) and compared with the same enum values. 27 | No types conversion happening 28 | 29 | ## Problems with enums 30 | 31 | Starts when enum values are converted. It is not a big deal to print values, you can assign string or number 32 | to the corresponding key. But parsing string into enum looks "strange" 33 | 34 | Given a situation when `MyEnumUserType` is now read out of an input, usually some string. 35 | 36 | ```ts 37 | const myUserType: string = "ADMIN"; 38 | const myUserInEnumType: MyEnumUserType = 39 | MyEnumUserType[myUserType as keyof typeof MyEnumUserType]; 40 | processUserData({ test: 3 }, myUserInEnumType); 41 | ``` 42 | 43 | Now, the function can be called but `as keyof typeof` looks as not needed, but required. 44 | 45 | # When to use union types instead of enum 46 | 47 | `MyEnumUserType` defined with union type: 48 | 49 | ```ts 50 | type UnionUserType = "ADMIN" | "REGISTERED" | "UNKNOWN"; 51 | ``` 52 | 53 | This is not so convinent because those hardcoded string constants are set as strings everywhere. 54 | 55 | So here is upgraded and more flexible version: 56 | 57 | ```ts 58 | const TypeAdmin = "ADMIN"; 59 | const TypeUnknown = "UNKNOWN"; 60 | const TypeRegistered = "REGISTERED"; 61 | 62 | type UpgradedUserType = typeof TypeAdmin | typeof TypeAdmin | typeof TypeAdmin; 63 | 64 | const myUserTypeAgain: string = "ADMIN"; // just a string 65 | const adminAssignedDirectly: UpgradedUserType = "ADMIN"; // already enum value 66 | 67 | const exportUserType = (userType: UpgradedUserType) => console.log(userType); 68 | 69 | const parseUserType = (userType: string): UpgradedUserType => 70 | userType as UpgradedUserType; 71 | ``` 72 | 73 | [open code in online editor](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAKgTwA4FMCCATAtgSzDAXhgCI0ARAWQEkA5YgbgChRJZFUBVMAazBAHd8RYhxoBpGgHkA6nSYto8ZCgBKKAOY5oKAE4oMhEioCiAcSoBlOMZNkGjRlGUwOSdToCGGfRwi72KIZOqCAAZkqomLj4AD4wwShhEejYeDBxCUkBUXhMzOCKWAi+-spo6h54AFww0Dp46oaklLQMMAD07TAAVgCuih61UPVg6vmsMF7RaBAQOOpg+mQ4esBQADYINa7uXj5+OgFN5NRyHV0e63peCDAoYL1YMABul70oDgqwKAAeSCA6KAlQ7OIgACn6pVQ2zcnm8GGBAQAlIQAHwwBQgdYoAB06xA6ghB2ReS+MCQHh0fkRoJgRKhKBqdQaSJhu3hNNQaMYMBgkJBXI8EBcsL2COJyiYQA) 74 | 75 | # Summary 76 | 77 | Use `enum` when it is not leaving codebase. 78 | 79 | Use `union type` as union replacement when your keys are parsed from some input. 80 | -------------------------------------------------------------------------------- /docs/privacy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | This static website collects only analytical data via Google Analytics. 4 | 5 | Please refer to Google Analytics Privacy Policy if you would like to clarify exactly what data is collected. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-practices", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "Oleg Makiienko", 7 | "license": "ISC", 8 | "scripts": { 9 | "clean": "./scripts/clean.sh", 10 | "markdown": "./scripts/markdown.sh", 11 | "format": "prettier --write ." 12 | }, 13 | "dependencies": { 14 | "@types/fs-extra": "^9.0.13", 15 | "@types/glob": "^7.2.0", 16 | "@types/jest": "^27.5.0", 17 | "@types/lz-string": "^1.3.34", 18 | "@types/node": "^17.0.31", 19 | "aws-sdk": "^2.1130.0", 20 | "axios": "^0.27.2", 21 | "fs-extra": "^10.1.0", 22 | "glob": "^8.0.1", 23 | "jest": "^28.1.0", 24 | "lz-string": "^1.4.4", 25 | "prettier": "^2.6.2", 26 | "ts-jest": "^28.0.1", 27 | "typescript": "^4.6.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | rm -Rf ./docs/pages 5 | 6 | # Compiled converter 7 | rm -f converter/**/*.js 8 | rm -f converter/**.js 9 | 10 | # Compiled sources 11 | rm -f src/**/*.js 12 | rm -f src/**.js 13 | -------------------------------------------------------------------------------- /scripts/markdown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | yarn tsc 5 | node converter/convert.js 6 | yarn format 7 | -------------------------------------------------------------------------------- /src/language/async-await.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Async 3 | * 4 | * The next iteration on async processing improvement. 5 | * 6 | * Functions are defined as `async` and executed with `await`. 7 | * 8 | * When `Promise` is awaited, resolver value will be returned and rejected thrown. 9 | */ 10 | const makeApiRequest = async (id: number): Promise => 11 | Promise.resolve(id); 12 | 13 | async function asyncExecution(): Promise { 14 | const first = await makeApiRequest(1); 15 | const second = await makeApiRequest(2); 16 | const third = await makeApiRequest(3); 17 | console.log(first + second + third); 18 | } 19 | asyncExecution(); 20 | /** 21 | * In Node.js async functions called in main scope and "awaited" by runtime 22 | * 23 | * `await` could be used with `async` function, function returning `Promise` or literal. 24 | * 25 | * > Generally `await` for literals should not be used ! 26 | * 27 | * This introduces errors and could be checked by `await-thenable` rule in `eslint-typescript` 28 | * 29 | * Here is classical issue when working with `aws-sdk`: 30 | */ 31 | import { DynamoDB } from "aws-sdk"; 32 | const dynamo = new DynamoDB(); 33 | 34 | const saveRecord = (data: any) => 35 | dynamo.putItem({ 36 | TableName: "xx", 37 | Item: data, 38 | }); 39 | 40 | async function criticalBug() { 41 | const data = {}; 42 | await saveRecord(data); 43 | console.log(`Item saved`); 44 | // Actually it was not yet saved 45 | // await waited for callback function. 46 | } 47 | criticalBug(); 48 | /** 49 | * Correctly will be to await for Promise: 50 | */ 51 | const saveRecordCorrect = (data: any): Promise => 52 | dynamo 53 | .putItem({ 54 | TableName: "xx", 55 | Item: data, 56 | }) 57 | .promise(); 58 | /** 59 | * Will be also useful to enable `eslint` rule to require return types on functions: `@typescript-eslint/explicit-function-return-type` 60 | * 61 | * This will require to specify `Promise` as return type explicitly and 62 | * potentially error will not happen 63 | * 64 | * ## Async errors handling 65 | * 66 | * Errors are captured with try-catch or propagated into surrounding scope. 67 | * This way wrapping code will catch all errors thrown within this code. 68 | * 69 | * Important to remember: Error will be thrown at a context where `await` is used. 70 | * 71 | * This is very common misconception 72 | */ 73 | const makeApiRequestRejection = async (): Promise => 74 | Promise.reject(1000); 75 | 76 | async function function1(): Promise { 77 | try { 78 | return await makeApiRequestRejection(); 79 | } catch (e: unknown) { 80 | console.error(`Api error in function1`); 81 | throw e; 82 | } 83 | } 84 | 85 | async function function2(): Promise { 86 | try { 87 | return await function1(); 88 | } catch (e: unknown) { 89 | console.error(`Api error in function2`); 90 | throw e; 91 | } 92 | } 93 | function2(); 94 | /** 95 | * Error will have a way thru all layers: 96 | * 97 | * - "Api error in function1" 98 | * - "Api error in function2" 99 | * - "(node:88361) UnhandledPromiseRejectionWarning: 1000" ... 100 | */ 101 | async function updatedFunction1(): Promise { 102 | try { 103 | return makeApiRequestRejection(); 104 | } catch (e: unknown) { 105 | console.error(`Api error in function1`); 106 | throw e; 107 | } 108 | } 109 | async function updatedFunction2(): Promise { 110 | try { 111 | return await updatedFunction1(); 112 | } catch (e: unknown) { 113 | console.error(`Api error in function2`); 114 | throw e; 115 | } 116 | } 117 | updatedFunction2(); 118 | 119 | /** 120 | * Here `updatedFunction1` returns function result without `await` 121 | * 122 | * Error will first appear only in surrounding block 123 | * 124 | * - "Api error in function2" 125 | * - "(node:88361) UnhandledPromiseRejectionWarning: 1000" ... 126 | * 127 | * Try-catch in `updatedFunction1` will not be used to handle error 128 | */ 129 | 130 | /** 131 | * # Key takeaways 132 | * 133 | * - Use `try-catch` if `await`-ed value can throw errors. 134 | * - Do not `await` for literal values, only thenable. 135 | * - Always explicitly provide returned type in async function definition 136 | */ 137 | -------------------------------------------------------------------------------- /src/language/basic-types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Any 3 | * 4 | * Could be disabled by compiler flag `--noImplicitAny` 5 | */ 6 | const anyValue: any = {}; 7 | /** 8 | * # Unknown 9 | * 10 | * Better not to use it explicitly 11 | * 12 | * Recently was added as a type for an error in `catch` 13 | */ 14 | 15 | const maybe: unknown = {}; 16 | 17 | const maybeSomething = {} as unknown; 18 | 19 | try { 20 | throw 42; 21 | } catch (err: unknown) {} 22 | 23 | /** 24 | * # Void 25 | * 26 | * Usually used to define that function do not return any value 27 | */ 28 | function none(): void {} 29 | function log(line: string): void { 30 | console.log(line); 31 | } 32 | 33 | /** 34 | * # String 35 | */ 36 | const str: string = "1"; // '111' 37 | const strTwo: string = `123`; 38 | 39 | /** 40 | * # Boolean 41 | */ 42 | const yes: boolean = true; 43 | 44 | /** 45 | * # Symbol 46 | * 47 | * Always unique, in practice `enum` is more adopted 48 | */ 49 | let sym1 = Symbol("something"); 50 | let symbolYes1 = Symbol("yes"); 51 | let symbolYes2 = Symbol("yes"); 52 | // symbolYes1 === symbolYes2 // false 53 | /** 54 | * # Numeric 55 | */ 56 | let num: number = 6; 57 | // Could have "_" separators to increase readability 58 | let readableNumber: number = 5_000_000_000; 59 | 60 | // Could be defined directly with oct/bin/hex literals 61 | let hex: number = 0xf00d; 62 | let binary: number = 0b1010; 63 | let octal: number = 0o744; 64 | 65 | // Available starting from `ES2020` (`tsconfig.json "target": "es2020"`) 66 | let big: bigint = 10000000000000000000000000000n; 67 | 68 | // Sometimes you will need this when looking for a maximum number in an array 69 | let maxNumber = Infinity; 70 | /** 71 | * # Arrays 72 | * 73 | * Could be defined by `[]` or `Array` generic 74 | */ 75 | const array: any[] = ["a", "b"]; 76 | const anotherArray: Array = [1, 2]; 77 | // 2 levels array definition 78 | const arrayComplex: string[][] = [ 79 | ["a", "b"], 80 | ["c", "d"], 81 | ]; 82 | // Arrays could mix different types. This is not very practical 83 | const mixedArray: (number | string | boolean)[] = [1, "2", true]; 84 | const strangeArray: (number | number[])[] = [1, [1]]; 85 | /** 86 | * # Tuple 87 | * 88 | * Do not confuse with `Array` 89 | */ 90 | const sampleTuple: [string, number, boolean] = ["a", 1, true]; 91 | /** 92 | * # Enum 93 | * 94 | * Without explicit values provided. This will by default apply numbers sequence starting from `0` in transpiled javascript code 95 | */ 96 | enum Status { 97 | OK, 98 | FAILURE, 99 | } 100 | const myStatus: Status = Status.OK; 101 | 102 | /** 103 | * With explicit values 104 | */ 105 | enum Counter { 106 | ONE = "a", 107 | TWO = "b", 108 | THREE = "c", 109 | } 110 | const myNumber: Counter = Counter.TWO; 111 | 112 | /** 113 | * # Undefined, null 114 | * 115 | * Undefined is usually used to define implicitly that nothing is there, it is empty, not defined 116 | * 117 | * Pure undefined 118 | */ 119 | let undef: undefined; 120 | const data1: undefined = [].find((x) => x > 0); 121 | /** 122 | * To represent "maybe" case, when value possibly is not set 123 | */ 124 | const data2: undefined | number = [1].find((x) => x > 0); 125 | /** 126 | * Usually is used for explicit "not set" but better to use `undefined` 127 | * */ 128 | let _null: null = null; 129 | 130 | /** 131 | * # Never 132 | * 133 | * Used in functions that will definitely not return 134 | */ 135 | function explode(): never { 136 | throw new Error("bam"); 137 | } 138 | 139 | /** 140 | * # Object 141 | * 142 | * Everything else except number, string, boolean, symbol, null, or undefined 143 | * 144 | * Generally, you won’t need to use this. 145 | * 146 | * `Object` type is like `any` amongst objects 147 | */ 148 | const data3: Object = {}; 149 | 150 | /** 151 | * # Function 152 | * 153 | * Please do not use `Function` type explicitly, it is like `any` amongst functions 154 | */ 155 | const func: Function = () => 1; 156 | -------------------------------------------------------------------------------- /src/language/callbacks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Callbacks 3 | * 4 | * Callbacks allowing to utilise so called "async nature" of `javascript` (and `typescript`) by detaching current 5 | * execution and continue it later. 6 | * 7 | * Defined in a way that function except data parameters have also `callback` as last parameter. 8 | * 9 | * Callback is a function with 2 parameters: 10 | * - `error` on a first place to reflect case when some issues happened, usually second parameter will not have value in this case 11 | * - `data` useful function result, in this case `err` usually is set explicitly to `null` or `undefined` 12 | * 13 | * Return type of both mentioned functions is `void`. Because no-one is waiting for them to return value synchronous. 14 | * 15 | * Callback still can use `return` inside, but it is used to stop its execution and exit. 16 | */ 17 | import fs from "fs"; 18 | fs.readFile( 19 | "filename", 20 | (err: NodeJS.ErrnoException | null, data: Buffer): void => { 21 | if (err) { 22 | return console.error(`Error occur: ${err}`); 23 | } 24 | console.log(`Data ${data.toString()}`); 25 | } 26 | ); 27 | /** 28 | * In this example callback function will be executed "after" file descriptor is returned by OS and file contents were read out. 29 | * 30 | * Function doing division, and returning an error when someone will try to divide by `0` 31 | */ 32 | // @playground-link 33 | const divide = ( 34 | a: number, 35 | b: number, 36 | cb: (err?: Error, result?: number) => void 37 | ) => { 38 | try { 39 | if (b === 0) { 40 | return cb(new Error(`Cant divide by 0`)); 41 | } 42 | cb(undefined, a / b); // this returns `Infinity` 43 | } catch (e: unknown) { 44 | if (e instanceof Error) cb(e); 45 | return cb(Error((e as any).toString())); 46 | } 47 | }; 48 | const callback = (err?: Error, result?: number) => { 49 | if (err) { 50 | return console.log("error happened", err); 51 | } 52 | console.log("result", result); 53 | }; 54 | divide(1, 1, callback); // prints: "result 1" 55 | divide(1, 0, callback); // prints: "error happened Error: Cant divide by 0" 56 | /** 57 | * When many callbacks are used it is easy to mess-up things and produce complex code 58 | */ 59 | // @playground-link 60 | import { stat, readFile, writeFile } from "fs"; 61 | 62 | type Err = NodeJS.ErrnoException | null; 63 | const fileToUpdate = "/tmp/some-file.txt"; 64 | 65 | stat(fileToUpdate, (err: Err, stats: any) => { 66 | if (err) { 67 | return console.error(`Stats error: ${err}`); 68 | } 69 | console.log(`File stats: ${stats}`); 70 | readFile(fileToUpdate, (errRead: Err, contents: any) => { 71 | if (errRead) { 72 | return console.error(`File reading error: ${errRead}`); 73 | } 74 | const newContents = `${contents}-updated`; 75 | // throw new Error(`Nobody will handle me`) 76 | writeFile(fileToUpdate, newContents, (errWrite: Err) => { 77 | if (errWrite) { 78 | return console.error(`File writing error: ${errRead}`); 79 | } 80 | console.log(`Write finished`); 81 | }); 82 | }); 83 | }); 84 | /** 85 | * Maintaining such code is complex and easy to make mistakes 86 | * 87 | * Currently unhandled errors will be just logged to stderr, if they are not given to callbacks. 88 | */ 89 | -------------------------------------------------------------------------------- /src/language/classes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Class definitions 3 | * 4 | * Originally to make classes, was used "prototype-based inheritance" when new functions were added into function prototype. 5 | * 6 | * This was complex and brought a lot of confusion. 7 | * 8 | * Staring from `ECMAScript 2015` classes were introduced 9 | */ 10 | // @playground-link 11 | type LogLevel = "error" | "debug"; 12 | class Logger { 13 | level: LogLevel; 14 | 15 | constructor(level: LogLevel) { 16 | this.level = level; 17 | } 18 | 19 | log(message: string) { 20 | console.log(`${this.level}: ${message}`); 21 | } 22 | } 23 | 24 | const logger = new Logger("debug"); 25 | logger.log("Test message"); 26 | 27 | /** 28 | * Try to use functional approaches always. 29 | * Your code should not have `class` definitions, believe you don't need them. 30 | */ 31 | 32 | /** 33 | * Inheritance in classes 34 | */ 35 | // @playground-link 36 | class BaseLogger { 37 | name: string; 38 | constructor(prefix: string) { 39 | this.name = prefix; 40 | } 41 | getFullMessage(message: string) { 42 | return `${this.name}: ${message}`; 43 | } 44 | } 45 | 46 | class ExtendedLogger extends BaseLogger { 47 | constructor(name: string) { 48 | super(name); // call base class constructor 49 | } 50 | getFullMessage(message: string) { 51 | //call base version of `getFullMessage` 52 | return `Extended: ${super.getFullMessage(message)}`; 53 | } 54 | } 55 | 56 | const extendedLogger = new ExtendedLogger("SampleLog"); 57 | console.log(extendedLogger.getFullMessage("some message")); // Extended: SampleLog: some message 58 | /** 59 | * Class members definition 60 | */ 61 | // @playground-link 62 | class PublicPrivate { 63 | // public by default 64 | name0: string = "test"; 65 | // private 66 | #name: string = "test"; 67 | // private 68 | private name2 = "test3"; 69 | // public 70 | public name3 = "test3"; 71 | // readonly (public) 72 | readonly canBeSetInConstructorOnly: boolean; 73 | 74 | constructor() { 75 | this.canBeSetInConstructorOnly = true; 76 | } 77 | private get() {} 78 | set() {} 79 | i_am_using_all() { 80 | this.#name; 81 | this.name2 = `${this.name2}something`; 82 | } 83 | } 84 | 85 | const publicPrivate = new PublicPrivate(); 86 | publicPrivate.name0; // ok 87 | publicPrivate.name3; // ok 88 | publicPrivate.set(); // ok 89 | -------------------------------------------------------------------------------- /src/language/dest-spread.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Destructuring 3 | * 4 | * Generally it is operation when complex object split into pieces and assigned to variables 5 | * 6 | * ## Tuples 7 | */ 8 | const tuple: [string, number, boolean] = ["test", 1, true]; 9 | /** 10 | * All elements 11 | */ 12 | const [e1, b2, c3] = tuple; 13 | /** 14 | * taking only some of elements 15 | */ 16 | const [firstOnly] = tuple; 17 | const [, secondElement] = tuple; 18 | const [first, , last] = tuple; 19 | /** 20 | * Not really convenient and easy to make "commas" mistake when trying to take only last element 21 | */ 22 | const [, , last_only] = tuple; 23 | /** 24 | * In combination with `spread` operator could take part of tuple into another tuple 25 | * 26 | * `tail` will be tuple: `[number, boolean]` 27 | */ 28 | const [head, ...tail] = tuple; 29 | 30 | /** 31 | * Common use case: return multiple values from a function 32 | */ 33 | const someValidation = (): [Object?, string[]?] => [{}]; 34 | const [result, maybeErrors] = someValidation(); 35 | 36 | /** 37 | * For arrays destructuring looks the same 38 | */ 39 | const numbersArray = [1, 2, 3, 4, 5]; 40 | const [arrayHead, ...arrayTail] = numbersArray; 41 | 42 | /** 43 | * # Object destructuring 44 | * 45 | * Allows to assign one or more values based on object properties 46 | */ 47 | interface SplitIntoPieces { 48 | error: string; 49 | data: number; 50 | probably?: number; 51 | } 52 | const { error, data, probably } = {} as SplitIntoPieces; 53 | 54 | /** 55 | * Destructuring deep properties 56 | */ 57 | interface DestroyMeDeep { 58 | message: string; 59 | data: { 60 | a: string; 61 | b: number; 62 | }; 63 | } 64 | const { 65 | data: { a, b }, 66 | } = {} as DestroyMeDeep; 67 | 68 | /** 69 | * Destructuring with changing parameter name 70 | */ 71 | const { 72 | message: someMessage, 73 | data: { a: aOther }, 74 | } = {} as DestroyMeDeep; 75 | 76 | /** 77 | * Destructuring: Common use case 78 | * Provide Dependency container to your application and pick services when they needed 79 | */ 80 | interface ApplicationConfig { 81 | database: Object; 82 | api: Object; 83 | logger: Object; 84 | config: Object; 85 | } 86 | const applicationConfigInstance = { 87 | database: {}, 88 | api: {}, 89 | logger: {}, 90 | config: {}, 91 | }; 92 | const { api } = applicationConfigInstance; 93 | 94 | /** 95 | * # Spread operator 96 | * 97 | * ## In functions 98 | * 99 | * It is opposite of destructuring 100 | * 101 | * Spread of function arguments from the tuple. 102 | * All parameters are required and tuple has fixed length, it is perfect match 103 | */ 104 | const args: [number, number, string] = [1, 2, "3"]; 105 | function spreadTuple(a: number, b: number, c: string): void {} 106 | spreadTuple(...args); 107 | 108 | /** 109 | * From an array. 110 | * 111 | * Array has more elements, so remaining will be ignored. 112 | * 113 | * Also due to array variable nature all function parameters should be optional and match type 114 | */ 115 | const args2 = [1, 2, 3, 4]; 116 | function spreadAccept(a?: number, b?: number): void {} 117 | spreadAccept(...args2); 118 | 119 | /** 120 | * Spread as function parameter 121 | * 122 | * `console.log` is the most common example of this practice 123 | */ 124 | function acceptAll(...strings: string[]): void {} 125 | acceptAll("a", "b", "c"); 126 | 127 | /** 128 | * ## In objects 129 | * 130 | * Could add properties from one object into another (merge). 131 | * 132 | * Usually used as alternative to `Object.assign()` 133 | */ 134 | interface BaseProperties { 135 | a: number; 136 | b: number; 137 | c: number; 138 | } 139 | interface ExtendedProperties extends BaseProperties { 140 | run: () => void; 141 | } 142 | const dataObject = { 143 | a: 1, 144 | b: 2, 145 | c: 3, 146 | }; 147 | const extendedDataObject: ExtendedProperties = { 148 | run: () => console.log("Running"), 149 | ...dataObject, 150 | }; 151 | /** 152 | * Important to keep spreads order. 153 | * 154 | * Often used to define `default` values 155 | */ 156 | const defaultUser = { 157 | name: "default name", 158 | email: "default email", 159 | }; 160 | const apiRequestData = { 161 | name: "Test", 162 | age: 21, 163 | }; 164 | const resultingUser = { 165 | userId: "abc", 166 | ...defaultUser, 167 | ...apiRequestData, 168 | }; 169 | /** 170 | * will result in: 171 | */ 172 | const resultingUserShape = { 173 | userId: "abc", 174 | name: "Test", 175 | age: 21, 176 | email: "default email", 177 | }; 178 | /** 179 | * Spread deep properties 180 | * 181 | * Each object should be specified explicitly like `address` here 182 | */ 183 | interface DeepProperties { 184 | name: string; 185 | names: string[]; 186 | age: number; 187 | address: { 188 | country: string; 189 | city: string; 190 | street: string; 191 | }; 192 | } 193 | const userA: DeepProperties = {} as DeepProperties; 194 | const userB: DeepProperties = {} as DeepProperties; 195 | 196 | const resultingUserAB: DeepProperties = { 197 | ...userA, 198 | ...userB, 199 | // merge arrays manually 200 | names: [...userA.names, ...userB.names], 201 | // merge "deep" objects manually 202 | address: { 203 | ...userA.address, 204 | ...userB.address, 205 | }, 206 | }; 207 | -------------------------------------------------------------------------------- /src/language/function.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Function definition 3 | * 4 | * Function parameters could be defined using required or optional notations 5 | */ 6 | function withOptionalArgs(a: string, b?: number): void {} 7 | function withOptionalDefaultArgs(a: string, b = 10): void {} 8 | /** 9 | * Optional parameters should be in the end of parameters definitions. 10 | * 11 | * Could be used spread operator to capture all parameters into an array 12 | */ 13 | function withPassThruArgs(...other: string[]): void {} 14 | /** 15 | * Function can have another function as parameter 16 | */ 17 | function convertToNumber( 18 | data: string, 19 | cb: (error: string | undefined, result?: number) => void 20 | ): void {} 21 | /** 22 | * Same rules applied to anonymous functions 23 | */ 24 | const withOptionalArgs2 = (a: string, b?: number): void => {}; 25 | const withOptionalDefaultArgs2 = (a: string, b = 10): void => {}; 26 | const withPassThruArgs2 = (a: string, ...other: string[]): void => {}; 27 | 28 | /** 29 | * Anonymous functions could be explicitly typed 30 | */ 31 | type LoggerFunction = (...params: string[]) => void; 32 | const logFunction: LoggerFunction = (...params: string[]) => { 33 | console.log(...params); 34 | }; 35 | const errorFunction: LoggerFunction = (...params: string[]) => { 36 | console.error(...params); 37 | }; 38 | 39 | /** 40 | * Function type can also be defined as `interface` 41 | * 42 | * This is not popular way, please don't do this, exists for backwards compatibility with javascript 43 | */ 44 | interface FunctionType { 45 | (a: string): string; 46 | } 47 | const stringInterfaceFunction: FunctionType = (a: string) => a.toLowerCase(); 48 | 49 | /** 50 | * Could be applied destructuring to the function parameters 51 | */ 52 | interface FunctionParams { 53 | a: string; 54 | b: number; 55 | } 56 | const appInParams = (first: number, { a }: FunctionParams): void => { 57 | console.log(first, a); 58 | }; 59 | 60 | /** 61 | * Destructuring with optional parameters and applied defaults 62 | * 63 | * Parameters order doesn't matter in this case. Optional default is not necessary at last position 64 | */ 65 | interface PartialFunctionOptionalObject { 66 | b: number; 67 | c?: string; 68 | d?: string; 69 | } 70 | const partialParamsOptional = ( 71 | a: number, 72 | { b, c = "defined", d }: PartialFunctionOptionalObject 73 | ): void => { 74 | console.log(a, b, c, d); // d = string | undefined, c = string 75 | }; 76 | /** 77 | * Provide default value when it is possible. 78 | */ 79 | const functionWithoutDefault = (name: string, data?: string[]) => { 80 | if (data) { 81 | // 'data' can be used now 82 | data.map((x) => x); 83 | } 84 | // OR this way: 85 | (data || []).map((x) => x); 86 | }; 87 | 88 | const functionWithDefault = (name: string, data: string[] = []) => { 89 | // 'data' can be used now 90 | }; 91 | 92 | /** 93 | * # Async functions 94 | * 95 | * As a good practice is to use `await `inside of `async` function otherwise it is just a function returning `Promise`. 96 | * 97 | * This is ensured by `eslint` rules, typescript itself allows `async` function without `await` 98 | */ 99 | const mainAsync = async (a: number): Promise => { 100 | return await Promise.resolve(a); 101 | }; 102 | const mainPromise = (): Promise => { 103 | return Promise.resolve(1); 104 | }; 105 | /** 106 | * Using any of these 107 | */ 108 | async function myFunction() { 109 | await mainAsync(1); // async function 110 | await mainPromise(); // Promise function 111 | } 112 | /** 113 | * In Node.js you could call `async` function on top level and runtime will internally wait it to be resolved 114 | */ 115 | myFunction(); 116 | 117 | /** 118 | * # Curried functions 119 | * 120 | * Functions returning functions. 121 | * 122 | * They used to keep some context till later time to execute action within that context 123 | */ 124 | type LongCurriedFunction = (a: string) => (code: number) => number; 125 | /** 126 | * It is better to define all intermediate types to increase readability 127 | */ 128 | type InternalFunction = (code: number) => number; 129 | type WrapperFunction = (a: string) => InternalFunction; 130 | 131 | const wrapperFunction: WrapperFunction = (a: string) => { 132 | console.log("wrapper", a); 133 | return (justCode: number) => { 134 | console.log("internal function", a, justCode); 135 | return justCode + 1; 136 | }; 137 | }; 138 | /** 139 | * Call one then second 140 | */ 141 | const partialFunction = wrapperFunction("test"); // => InternalFunction 142 | partialFunction(200); // => 201 143 | /** 144 | * Call both same time. Pretty useless scenario 145 | */ 146 | wrapperFunction("test")(200); // => 201 147 | /** 148 | * # Short notation 149 | * 150 | * Can be used when function have only one operation which gives result and it could be immediately returned 151 | * 152 | * > Effectively it removes `{}` and `return` 153 | */ 154 | const shortNotation = (a: string, b: number) => `${a} + ${b}`; 155 | 156 | const shortCurried = (a: string) => (b: number) => (c: string) => 157 | `${a} -> ${b} -> ${c}`; 158 | /** 159 | * Return an object as only one operation in function 160 | */ 161 | const addAndReturn = (a: string, b: string) => ({ 162 | sum: a + b, 163 | }); 164 | /** 165 | * Here, extra `()` is required because `{}` in function definition used to define function body. 166 | * 167 | * Here are same function definitions: add1, add2 168 | */ 169 | function add1(a: number, b: number) { 170 | return a + b; 171 | } 172 | const add2 = (a: number, b: number) => a + b; 173 | add1(1, 2); 174 | add2(1, 2); 175 | 176 | /** 177 | * 178 | */ 179 | 180 | /** 181 | * # "this" capturing 182 | * 183 | * Main difference between `function` and `const` function definitions is the way how they works with `this` 184 | * 185 | * In this sample `set` and `setClassical` doing the same thing, but are different 186 | * because of definitions used 187 | */ 188 | function practiceThisWrapper() { 189 | // Curried function type 190 | type Setter = (a: string) => (a: string) => void; 191 | // Object type definition in needed for `this` usage inside of it's functions 192 | interface CapturingObject { 193 | data: Record; 194 | set: Setter; 195 | setClassical: Setter; 196 | } 197 | const thisCapturingObject: CapturingObject = { 198 | data: {}, 199 | set: function (key: string) { 200 | // Next arrow function is capturing `this` as current scope 201 | // classical `function` will not work when using `this` inside 202 | return (value: string) => { 203 | this.data[key] = value; 204 | }; 205 | }, 206 | setClassical: function (key: string) { 207 | // keep `this` for the `function` 208 | const self = this; 209 | return function (value: string) { 210 | self.data[key] = value; 211 | }; 212 | }, 213 | // It is wrong definition: 214 | // `this` will be referencing scope of `thisPracticeWrapper` 215 | // setShort: (key: string) => (value: string) => { 216 | // this.data[key] = value; 217 | // }, 218 | }; 219 | thisCapturingObject.set("data")("value"); 220 | thisCapturingObject.setClassical("data2")("value2"); 221 | console.log(thisCapturingObject.data); 222 | // { data: 'value', data2: 'value2' } 223 | } 224 | practiceThisWrapper(); 225 | /** 226 | * As a recommendation here is to use builder to create the object. 227 | * This will not require to use `this` and will be more transparent 228 | */ 229 | function practiceThisBuilderWrapper() { 230 | // Curried function type 231 | type Setter = (a: string) => (a: string) => void; 232 | // Object type definition in needed for `this` usage inside of it's functions 233 | interface CapturingObject { 234 | data: Record; 235 | set: Setter; 236 | setClassical: Setter; 237 | } 238 | const buildCapturingObject = (): CapturingObject => { 239 | const data: Record = {}; 240 | // both are `arrow function` definitions 241 | const set = (key: string) => { 242 | return (value: string) => { 243 | data[key] = value; 244 | }; 245 | }; 246 | // both are `function` definitions 247 | function setClassical(key: string) { 248 | return function (value: string) { 249 | data[key] = value; 250 | }; 251 | } 252 | return { 253 | data, 254 | set, 255 | setClassical, 256 | }; 257 | }; 258 | const thisCapturingObject = buildCapturingObject(); 259 | thisCapturingObject.set("data")("value"); 260 | thisCapturingObject.setClassical("data2")("value2"); 261 | console.log(thisCapturingObject.data); 262 | // { data: 'value', data2: 'value2' } 263 | } 264 | practiceThisBuilderWrapper(); 265 | -------------------------------------------------------------------------------- /src/language/generics.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * # Generics 4 | * 5 | * Usual use cases: 6 | * - when actual type is not important 7 | * - when solution should be reusable for some cases 8 | * 9 | * ## Function Generics 10 | * 11 | * Placeholders `TYPE` and `O` could be named any way, preferable to have one capital letter. 12 | * 13 | * Generic function type to be reusable 14 | */ 15 | function logSomething(data: TYPE, param: O): void {} 16 | const logSomethingAgain = (data: TYPE): void => {}; 17 | /** 18 | * pass-thru generic function `wrapper`: 19 | * */ 20 | const handler = (data: any): void => {}; 21 | const wrapper = (param: T) => { 22 | handler(param); 23 | }; 24 | 25 | /** 26 | * # Type Generic 27 | * 28 | * Optional type defined this way 29 | */ 30 | type Optional = T | undefined; 31 | type OptionalNumber = Optional; 32 | /** 33 | * Could have 2 values 34 | */ 35 | const maybeInt: OptionalNumber = 1; 36 | const maybeAnotherOne: OptionalNumber = undefined; 37 | 38 | /** 39 | * # Interface Generic 40 | * 41 | */ 42 | // @playground-link 43 | interface GenericTree { 44 | value: L; 45 | left?: GenericTree; 46 | right?: GenericTree; 47 | } 48 | const stringTree: GenericTree = { 49 | value: "h", 50 | left: { 51 | value: "b", 52 | right: { 53 | value: "c", 54 | }, 55 | }, 56 | }; 57 | const numberTree: GenericTree = { 58 | value: 10, 59 | left: { 60 | value: 3, 61 | right: { 62 | value: 8, 63 | }, 64 | }, 65 | }; 66 | console.log(stringTree); 67 | console.log(numberTree); 68 | 69 | /** 70 | * Using multiple type placeholders 71 | */ 72 | type TwoPlaceholdersType = (input: A) => B; 73 | /** 74 | * Make type aliasing 75 | */ 76 | // @playground-link 77 | type Converter = (input: A) => B; 78 | 79 | type ConvertToNumber = Converter; 80 | type ConvertToString = Converter; 81 | 82 | const toNumberConverter: ConvertToNumber = (input: string): number => 83 | parseInt(input); 84 | 85 | const toStringConverter: ConvertToString = (input: number): string => 86 | input.toString(); 87 | 88 | console.log(toNumberConverter("100")); 89 | console.log(toStringConverter(200)); 90 | /** 91 | * Generics based on type boundaries. 92 | * 93 | * Here `array: A[]` showing that array methods like `.map`, `.reduce` etc will be available 94 | */ 95 | const arrayMap = (array: A[], func: (x: A) => B): B[] => array.map(func); 96 | /** 97 | * Custom type as generic type base 98 | * 99 | * `` showing that `T` should be subtype of `Sizable` 100 | */ 101 | // @playground-link 102 | interface Sizable { 103 | size: number; 104 | } 105 | 106 | interface SizableCloth { 107 | name: string; 108 | size: number; 109 | } 110 | 111 | interface SizableHat { 112 | radius: number; 113 | size: number; 114 | } 115 | 116 | const sumAllSizes = (array: T[]): number => 117 | array.reduce((p: number, c: T) => c.size + p, 0); 118 | const hat: SizableHat = { 119 | size: 5, 120 | radius: 1, 121 | }; 122 | const cloth: SizableCloth = { 123 | size: 10, 124 | name: "cool", 125 | }; 126 | const resultSum = sumAllSizes([hat, cloth]); // => 15 127 | console.log(resultSum); 128 | 129 | /** 130 | * Generic type definition based on another generic type 131 | * 132 | * Index Type Query or `keyof`: It yields the type of permitted property names for a give type. 133 | * `extends keyof` will result in having `K` as string with values as property names of `O` 134 | * 135 | * `O[K]` will be type of the value accessible in `O` when using `K` property 136 | */ 137 | const getProperty = (obj: O, key: K): O[K] => obj[key]; 138 | /** 139 | * Here `a` have type `number` so result of `getProperty` will the `number` 140 | */ 141 | const numberProp: number = getProperty({ a: 1 }, "a"); 142 | /** 143 | * Here `b` have type `string` so result of `getProperty` will the `string` 144 | */ 145 | const stringProp: string = getProperty({ a: 1, b: "1" }, "b"); 146 | -------------------------------------------------------------------------------- /src/language/iterate.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Iterate array items 3 | * 4 | * ## For-of 5 | * 6 | * Iterate over "values" in the given array 7 | */ 8 | // @playground-link 9 | const array1 = ["one", "two", "three"]; 10 | for (let entry of array1) { 11 | console.log(entry); 12 | // "one" 13 | // "two" 14 | // "three" 15 | } 16 | /** 17 | * ## For-in 18 | * 19 | * Iterate over "keys" in the given array 20 | */ 21 | // @playground-link 22 | const array2 = ["one", "two", "three"]; 23 | for (let entry in array2) { 24 | console.log(entry, typeof entry); 25 | // "0" "string" 26 | // "1" "string" 27 | // "2" "string" 28 | } 29 | /** 30 | * ## Iterate key-value 31 | * 32 | * The `.entries()` method returns a new Array Iterator object that contains the key/value pairs for each index in the array. 33 | */ 34 | // @playground-link 35 | const array3 = ["one", "two", "three"]; 36 | for (let [key, value] of array3.entries()) { 37 | console.log(key, value, typeof key, typeof value); 38 | // 0, "one", "number", "string" 39 | // 1, "two", "number", "string" 40 | // 2, "three", "number", "string" 41 | } 42 | /** 43 | * # Objects 44 | * 45 | * ## For-of 46 | * 47 | * To have the same approach as for an array, use `Object.values()` to iterate over values 48 | */ 49 | // @playground-link 50 | const obj1 = { one: 10, two: 20, three: 30 }; 51 | for (let v of Object.values(obj1)) { 52 | console.log(v); 53 | // 10 54 | // 20 55 | // 30 56 | } 57 | /** 58 | * ## For-in 59 | * 60 | * Same as for arrays, iterate over keys 61 | */ 62 | const obj2 = { one: 10, two: 20, three: 30 }; 63 | for (let k in obj2) { 64 | console.log(k); 65 | // "one" 66 | // "two" 67 | // "three" 68 | } 69 | /** 70 | * Can be used `Object.keys()` to transform obejct keys into the array and later can be iterated with `for-of` 71 | * 72 | * ## Iterate key-value 73 | * 74 | * Objects dont have function `entries`, but it is available via: `Object.entries()` 75 | */ 76 | // @playground-link 77 | const obj3 = { one: 10, two: 20, three: 30 }; 78 | for (let [k, v] of Object.entries(obj3)) { 79 | console.log(k, v, typeof k, typeof v); 80 | // "one", 10, "number", "string" 81 | // "two", 20, "number", "string" 82 | // "three", 30, "number", "string" 83 | } 84 | -------------------------------------------------------------------------------- /src/language/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Using Promise 3 | * 4 | * `Promise` can be either `pending` or `fulfilled` or `rejected`. `fulfilled` and `rejected` are representing results. 5 | * 6 | * Every promise should eventually be resolved or rejected 7 | */ 8 | const promiseSample: Promise = Promise.resolve(1); 9 | /** 10 | * Dummy result wrappers can be created directly 11 | */ 12 | Promise.resolve(1); 13 | Promise.reject(1); 14 | /** 15 | * Constructor 16 | * ``` 17 | * new (executor: ( 18 | * resolve: (value?: T | PromiseLike) => void, 19 | * reject: (reason?: any) => void) => void 20 | * ): Promise; 21 | * ``` 22 | */ 23 | new Promise( 24 | (resolve: (data: number) => void, reject: (reason: any) => void) => { 25 | try { 26 | resolve(1 + 1); 27 | } catch (e) { 28 | reject(e); 29 | } 30 | } 31 | ); 32 | 33 | /** 34 | * Promise transforms callback approach into chaining of error or result handlers 35 | */ 36 | import axios from "axios"; 37 | axios 38 | .get("/user", { 39 | params: { 40 | ID: 12345, 41 | }, 42 | }) 43 | .then((response) => { 44 | // process response 45 | console.log(response); 46 | }) 47 | .catch((error) => { 48 | // process error 49 | console.log(error); 50 | }) 51 | .then(() => { 52 | // always executed 53 | }); 54 | /** 55 | * Chain calls 56 | */ 57 | Promise.resolve(1) 58 | .then((result) => result + 1) 59 | .then((result) => { 60 | console.log(result); // 2 61 | return result; 62 | }) 63 | .then((result) => result + 1) 64 | .then((result) => { 65 | console.log(result); // 3 66 | return result; 67 | }); 68 | /** 69 | * Catching errors in one place for all `then` cases 70 | */ 71 | 72 | Promise.resolve(1) 73 | .then((result) => result + 1) 74 | .then((result) => result / 0) 75 | .then((result) => result * 1) 76 | .catch((error) => { 77 | console.error(error); 78 | }); 79 | 80 | /** 81 | * Catching expected errors in the middle of the processing 82 | */ 83 | 84 | Promise.resolve(1) 85 | .then((result) => result / 0) 86 | .catch((error) => { 87 | // catch division by zero 88 | console.error(error); 89 | // it will be Promise.resolve(0) 90 | // so `.then` chaining could be used again 91 | return 0; 92 | }) 93 | .then((result) => result + 1) 94 | .then((result) => result * 1); 95 | 96 | /** 97 | * Chaining promise functions 98 | */ 99 | interface UserShape { 100 | userId: number; 101 | name: string; 102 | } 103 | const loadUser = (userId: number): Promise => 104 | Promise.resolve({ 105 | userId: 1, 106 | name: "Some user", 107 | }); 108 | 109 | const capitalizeUser = (user: UserShape): Promise => 110 | Promise.resolve({ 111 | ...user, 112 | name: user.name.toUpperCase(), 113 | }); 114 | 115 | const logUser = (user: UserShape): Promise => { 116 | console.log(user); 117 | return Promise.resolve(user); 118 | }; 119 | 120 | const sendUser = (user: UserShape): Promise => axios.post("url", user); 121 | 122 | loadUser(42) 123 | .then(capitalizeUser) 124 | .then(logUser) 125 | .then(sendUser) 126 | .catch(console.error); 127 | /** 128 | * Transforming callback function into Promise based 129 | */ 130 | 131 | const plusOneCallback = ( 132 | value: number, 133 | cb: (err: Error | undefined, result: number) => void 134 | ) => cb(undefined, value + 1); 135 | 136 | const plusOnePromise = (value: number) => 137 | new Promise((resolve, reject) => { 138 | plusOneCallback(value, (error, data) => { 139 | if (error) { 140 | return reject(error); 141 | } 142 | resolve(data); 143 | }); 144 | }); 145 | 146 | plusOnePromise(42).then(console.log); 147 | /** 148 | * ## Promise helper functions 149 | * 150 | * ### Promise.all 151 | * 152 | * Returns a single Promise that resolves to an array of the results of the input promises 153 | * 154 | * > It rejects immediately upon any of the input promises rejecting 155 | */ 156 | const promise1 = Promise.resolve(3); 157 | const promise2 = 42; 158 | const promise3 = Promise.resolve(2); 159 | 160 | Promise.all([promise1, promise2, promise3]).then((values) => { 161 | console.log(values); // [3, 42, 2] 162 | }); 163 | 164 | /** 165 | * Rejection in Promise.all() 166 | */ 167 | const promise11 = Promise.resolve(3); 168 | const promise22 = Promise.reject(42); 169 | const promise33 = new Promise((resolve) => { 170 | setTimeout(() => resolve(100), 100); 171 | }); 172 | 173 | Promise.all([promise11, promise22, promise33]) 174 | .then((values) => { 175 | console.log(values); 176 | }) 177 | .catch(console.error); // => 42 178 | /** 179 | * ### Promise.allSettled 180 | * 181 | * Method returns a promise that resolves after all of the given promises have either fulfilled or rejected, 182 | * with an array of objects that each describes the outcome of each promise. 183 | * 184 | * > You usually want to use `.allSettled` instead of `.all` 185 | */ 186 | function settledExample() { 187 | const promise100 = Promise.resolve(3); 188 | const promise200 = new Promise((resolve, reject) => { 189 | setTimeout(() => reject(100), 100); 190 | }); 191 | Promise.allSettled([promise100, promise200]).then((results) => { 192 | console.log(results); 193 | // [ 194 | // { status: 'fulfilled', value: 3 }, 195 | // { status: 'rejected', reason: 100 } 196 | // ] 197 | const finishedValues = results 198 | .filter((x) => x.status === "fulfilled") 199 | .map((x) => (x as PromiseFulfilledResult).value); 200 | console.log(finishedValues); // [ 3 ] 201 | }); 202 | } 203 | settledExample(); 204 | /** 205 | * ### Promise.race 206 | * 207 | * Method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, 208 | * with the value or reason from that promise. 209 | */ 210 | 211 | function raceSample() { 212 | const apiCallSuccess = new Promise((res, rej) => 213 | setTimeout(() => res("Done"), 100) 214 | ); 215 | const apiCallFailure = new Promise((res, rej) => 216 | setTimeout(() => rej("Error"), 100) 217 | ); 218 | const timeoutPromise = (ms = 300) => 219 | new Promise((res, rej) => setTimeout(rej, ms)); 220 | 221 | const requestWithTimeout = (request: Promise, timeout = 300) => 222 | Promise.race([timeoutPromise(timeout), request]); 223 | 224 | requestWithTimeout(apiCallSuccess).then(console.log); 225 | requestWithTimeout(apiCallFailure).catch(console.error); 226 | } 227 | raceSample(); 228 | 229 | /** # More info 230 | * 231 | * Check [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 232 | */ 233 | -------------------------------------------------------------------------------- /src/language/type-interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Type 3 | * 4 | * Simple type aliasing 5 | */ 6 | 7 | type CustomerName = string; 8 | /** 9 | * Useful when function have many parameters and it is easy to make mistakes when passing them. Just extract `string` as domain specific type 10 | */ 11 | 12 | const customerName: CustomerName = "Oleg"; 13 | /** 14 | * Mixed types 15 | */ 16 | type MaybeString = string | undefined; 17 | type SomethingNobodyExpects = string | number | boolean; 18 | /** 19 | * Represent multiple possibilities when defining error 20 | */ 21 | type Errors = string | string[] | undefined; 22 | /** 23 | * Type defined out of fixed literals 24 | */ 25 | type FixedNumbers = 1 | 2 | 3 | 4; 26 | type FixedStrings = "ONE" | "TWO" | "THREE"; 27 | /** 28 | * Boolean looks strange, but still used 29 | */ 30 | type IamAlwaysTrue = true; 31 | type IamAlwaysFalse = false; 32 | /** 33 | * Complex object defined as new type 34 | */ 35 | type SomethingBigger = { 36 | a: number; 37 | b: number; 38 | }; 39 | 40 | /** 41 | * # Recursive types 42 | * 43 | * Could include itself in definitions 44 | */ 45 | // @playground-link 46 | type Tree = { 47 | value: number; 48 | left?: Tree; 49 | right?: Tree; 50 | }; 51 | const tree: Tree = { 52 | value: 10, 53 | left: { 54 | value: 5, 55 | right: { 56 | value: 7, 57 | }, 58 | }, 59 | }; 60 | console.log(tree); 61 | /** 62 | * # Combining type definitions 63 | * 64 | * Done with `|` (OR) or `&` (AND) 65 | * 66 | * `MyError` could be any of listed 67 | */ 68 | class MyErrorClass {} 69 | type MyError = Error | MyErrorClass; 70 | 71 | /** 72 | * And case `&` used to merge types into one 73 | */ 74 | // @playground-link 75 | type WithNumbers = { 76 | one: number; 77 | two: number; 78 | }; 79 | type WithStrings = { 80 | three: string; 81 | four: string; 82 | }; 83 | type CombinedObject = WithNumbers & WithStrings; 84 | const combined: CombinedObject = { 85 | one: 1, 86 | two: 2, 87 | three: "3", 88 | four: "4", 89 | }; 90 | console.log(combined.one); 91 | console.log(combined.three); 92 | /** 93 | * All properties with same name will have resulting type `never`. Do not do this ! 94 | */ 95 | 96 | /** 97 | * # Type vs Interface 98 | * 99 | * Type generally is used for one liners, simple cases with `|` and `&` or functions 100 | * 101 | * Interface is used for complex constructs 102 | */ 103 | type GoodType = string | string[] | undefined; 104 | type GoodFunctionType = (a: string, b: string) => string; 105 | interface GoodDataInterface { 106 | customerId: number; 107 | age: number; 108 | email: string; 109 | } 110 | /** 111 | * # Interface 112 | * 113 | * Could have optional properties 114 | */ 115 | interface WithOptionalProps { 116 | definitelyHere: number; 117 | goodDefinedOptional?: string; // prefer this way to define optional 118 | notSoGood: string | undefined; 119 | } 120 | /** 121 | * Represent partially undefined shape. 122 | * `userId` should be there and everything else could present but not required to know how it is looks like 123 | */ 124 | interface JsonDecodedData { 125 | userId: string; 126 | [anyNameHere: string]: any; 127 | } 128 | /** 129 | * Usually keys are of type`string`. `number` is also allowed but it is not convenient to use it. 130 | * 131 | * Common use case is to parse json body and pass it to next service 132 | */ 133 | const body = `{"userId": "1", "age":21, "name":"Bob"}`; 134 | const apiRequest = JSON.parse(body) as JsonDecodedData; 135 | if (apiRequest.userId !== undefined) { 136 | console.log(apiRequest); 137 | } 138 | 139 | /** 140 | * # Extend interface 141 | */ 142 | interface Base { 143 | length: number; 144 | } 145 | interface Coordinate { 146 | x: number; 147 | y: number; 148 | } 149 | /** 150 | * New interface extending others and adding additional properties 151 | */ 152 | interface Field extends Base, Coordinate { 153 | name: string; 154 | } 155 | const myField: Field = { 156 | length: 100, 157 | x: 10, 158 | y: 20, 159 | name: "My stuff", 160 | }; 161 | /** 162 | * New interface extending others but without any additional properties 163 | */ 164 | interface NoNameField extends Base, Coordinate {} 165 | const somebodyField: NoNameField = { 166 | length: 100, 167 | x: 10, 168 | y: 20, 169 | }; 170 | /** 171 | * Could be also defined by `type` 172 | */ 173 | type NoNameFieldType = Base & Coordinate; 174 | 175 | /** 176 | * Interface could force readonly properties 177 | */ 178 | interface TryToAssign { 179 | a?: number; 180 | readonly b: string; 181 | } 182 | const readOnlyObject: TryToAssign = { b: "b" }; 183 | readOnlyObject.a = 10; 184 | /** 185 | * This will not work: `readOnlyObject.b = "10"` 186 | */ 187 | -------------------------------------------------------------------------------- /src/language/utility-types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Utility Types 3 | * 4 | * There are utility types provided by typescript which are useful for common use cases 5 | * 6 | * ## Partial 7 | * 8 | * Allows to make all object properties "optional" 9 | */ 10 | // @playground-link 11 | interface FullType { 12 | first: string; 13 | last: string; 14 | } 15 | const fullData: FullType = { 16 | first: "a", 17 | last: "b", 18 | }; 19 | const partialData: Partial = { 20 | last: "test", 21 | }; 22 | console.log(partialData); 23 | /** 24 | * Commonly used for partial data merge functions 25 | */ 26 | // @playground-link 27 | interface FullUserMerge { 28 | first: string; 29 | last: string; 30 | email: string; 31 | gender: string; 32 | } 33 | const fullUser: FullUserMerge = { 34 | first: "a", 35 | last: "b", 36 | email: "test-email", 37 | gender: "f", 38 | }; 39 | const mergeDataIntoUser = ( 40 | user: FullUserMerge, 41 | partialData: Partial 42 | ): FullUserMerge => ({ 43 | ...user, 44 | ...partialData, 45 | }); 46 | 47 | const mergedData1 = mergeDataIntoUser(fullUser, { gender: "f" }); 48 | const mergedData2 = mergeDataIntoUser(fullUser, { 49 | email: "some-email", 50 | last: "x", 51 | }); 52 | console.log(mergedData1); 53 | console.log(mergedData2); 54 | 55 | /** 56 | * ## Required 57 | * 58 | * Opposite of `Partial`, makes all properties required 59 | */ 60 | // @playground-link 61 | interface MaybeSomeOptions { 62 | db?: string; 63 | apiUrl?: string; 64 | api: string; // this is not required, it will stay as is 65 | } 66 | type AllRequiredOptions = Required; 67 | const allRequiredOptions: AllRequiredOptions = { 68 | db: "db-name", // required now 69 | apiUrl: "google.com", // required now 70 | api: "api-url", // required now 71 | }; 72 | console.log(allRequiredOptions); 73 | 74 | /** ## Record 75 | * 76 | * Allows to build an object type using 2 types: one for key and second for value 77 | */ 78 | type FixedMap = Record<"a" | "b", number>; 79 | const fixedTypeInstance: FixedMap = { 80 | a: 1, 81 | b: 2, 82 | // c: 3,// ... and 'c' does not exist in type 83 | }; 84 | /** 85 | * These 2 definitions are equal: 86 | */ 87 | type ObjectWithStringKeys = { [key: string]: any }; 88 | type RecordWithStringKeys = Record; 89 | /** 90 | * This type is very useful when object shape is not fully known. 91 | * 92 | * Commonly used as a type for json parsing results, 93 | * json should have keys as a `string` but contain many various "values" 94 | */ 95 | const jsonData: Record = JSON.parse(`{"nobody":"knows"}`); 96 | /** 97 | * Any type could be used to represent value 98 | */ 99 | // @playground-link 100 | interface DynamoDbRecordType { 101 | userId: string; 102 | data: number; 103 | } 104 | const recordsByUserId: Record = { 105 | "1": { 106 | userId: "1", 107 | data: 100, 108 | }, 109 | "2": { 110 | userId: "2", 111 | data: 200, 112 | }, 113 | }; 114 | console.log(recordsByUserId); 115 | 116 | /** 117 | * ## Readonly 118 | * 119 | * Set all properties of an object as `readonly` 120 | */ 121 | // @playground-link 122 | interface DynamicInterface { 123 | a: string; 124 | b: number; 125 | } 126 | type ReadonlyInterface = Readonly; 127 | const readOnly: ReadonlyInterface = { 128 | a: "x", 129 | b: 1, 130 | }; 131 | console.log(readOnly); 132 | /** 133 | * readOnly.a = "xx"; // Cannot assign to 'a' because it is a read-only property. 134 | */ 135 | 136 | /** ## Pick 137 | * 138 | * Constructs new type by "picking" properties from another type. 139 | * 140 | * In practice not so often used. 141 | */ 142 | // @playground-link 143 | interface FullBigType { 144 | one: number; 145 | two: number; 146 | three: number; 147 | four: number; 148 | five: number; 149 | } 150 | type OneAndTwo = Pick; 151 | const oneOrTwo: OneAndTwo = { 152 | one: 1, 153 | two: 2, 154 | // three is not here 155 | }; 156 | console.log(oneOrTwo); 157 | /** ## Omit 158 | * 159 | * Removes some properties from type definition 160 | */ 161 | // @playground-link 162 | interface DatabaseUserSecret { 163 | userId: string; 164 | login: string; 165 | password: string; 166 | name: string; 167 | } 168 | type DatabaseUserPublic = Omit; 169 | const privateDbUser: DatabaseUserSecret = { 170 | userId: "1", 171 | login: "username", 172 | password: "secret", 173 | name: "My Name", 174 | }; 175 | const publicDbUser: DatabaseUserPublic = { 176 | userId: "1", 177 | name: "My Name", 178 | // login: "username", // ...and 'login' does not exist in type 179 | // password: "secret", // ...and 'password' does not exist in type 180 | }; 181 | console.log(privateDbUser); 182 | console.log(publicDbUser); 183 | /** 184 | * This type is useful for a cases when type of some properties should be changed 185 | */ 186 | // @playground-link 187 | interface DynamodbItemPlain { 188 | recordId: string; 189 | createdAt: string; 190 | updatedAt: string; 191 | data: string; 192 | } 193 | 194 | interface DynamodbItemData { 195 | userId: string; 196 | name: string; 197 | } 198 | interface DynamodbItemParsed extends Omit { 199 | data: DynamodbItemData; 200 | } 201 | const plainDbItem: DynamodbItemPlain = { 202 | recordId: "record-id", 203 | createdAt: "2020-01-01", 204 | updatedAt: "2020-02-02", 205 | data: `{"userId":"user-id", "name":"User Name"}`, 206 | }; 207 | const convert = (origin: DynamodbItemPlain): DynamodbItemParsed => ({ 208 | ...origin, 209 | data: JSON.parse(origin.data), 210 | }); 211 | const parsedDbData = convert(plainDbItem); 212 | console.log(parsedDbData); // this will have data as `DynamodbItemData` 213 | 214 | /** 215 | * ## Other types 216 | * 217 | * Check more utility types at official [docs page](https://www.typescriptlang.org/docs/handbook/utility-types.html) 218 | * 219 | * `Exclude`, `Extract`, `NonNullable`, `ReturnType` etc are not so often used 220 | */ 221 | -------------------------------------------------------------------------------- /src/language/variables.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Variable definitions 3 | * 4 | * Classical `var` is working to be compatible with javascript 5 | */ 6 | var simpleVar = 1; 7 | /** 8 | * It has problems with scoping, so it is usually forbidden to use by tsc options 9 | * 10 | * Recommended to use: 11 | * 12 | * - `let` for variables changing values 13 | * - `const` for constants having one value all time. 14 | */ 15 | const databaseAdapter = {}; 16 | let index = 0; 17 | 18 | /** 19 | * You could still modify internal properties of objects and arrays defined with `const`: 20 | */ 21 | const object = { 22 | a: 1, 23 | b: 2, 24 | }; 25 | object.a = 10; 26 | // object = {a: 10, b:2 } // will not work 27 | const myArray = []; 28 | myArray.push(10); // ok 29 | /** 30 | * Object key short notation 31 | */ 32 | const userId = 10; 33 | const userObject = { 34 | userId, // this mean: take `userId` as property name and assign value from `userId` variable 35 | }; 36 | /** 37 | * # Block-scoping 38 | */ 39 | // @playground-link 40 | function checkForError(isError: boolean) { 41 | let errorPrefix = "Error: "; 42 | if (isError) { 43 | let fullError = errorPrefix + "here"; 44 | return fullError; 45 | } 46 | // @ts-ignore 47 | return fullError; // Error: 'fullError' doesn't exist here 48 | } 49 | 50 | /** 51 | * `let` works within scope and could be shadowed within child scope 52 | */ 53 | // @playground-link 54 | function checkForErrorAnother(isError: boolean) { 55 | const errorPrefix = "Error: "; 56 | if (isError) { 57 | const errorPrefix = "Internal Error: "; 58 | let fullError = errorPrefix + "here"; 59 | return fullError; 60 | } 61 | return errorPrefix + " not happened"; 62 | } 63 | const result1 = checkForErrorAnother(true); // "Internal Error: here" 64 | const result2 = checkForErrorAnother(false); // "Error: not happened" 65 | console.log(result1); 66 | console.log(result2); 67 | -------------------------------------------------------------------------------- /src/topics/array-async.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Async and arrays 3 | * 4 | * ## Sequential for 5 | * 6 | * Use `for` for sequential async function execution 7 | */ 8 | // @playground-link 9 | const sequentialAsync = async () => { 10 | const data = [5, 7, 3, 10, 40]; 11 | 12 | const apiCall = (ms: number): Promise => 13 | new Promise((res) => { 14 | console.log(`-> ${ms}`); 15 | setTimeout(() => { 16 | console.log(`<- ${ms}`); 17 | res(ms * 100); 18 | }, ms); 19 | }); 20 | 21 | const asyncRes = []; 22 | for (const i of data) { 23 | asyncRes.push(await apiCall(i)); 24 | } 25 | console.log(asyncRes); 26 | }; 27 | sequentialAsync(); 28 | /** 29 | * Expected output: 30 | ``` 31 | -> 5 32 | <- 5 33 | -> 7 34 | <- 7 35 | -> 3 36 | <- 3 37 | -> 10 38 | <- 10 39 | -> 40 40 | <- 40 41 | [ 500, 700, 300, 1000, 4000 ] 42 | ``` 43 | */ 44 | /** 45 | * ## Parallel async map 46 | * 47 | * Using `.map` for parallel async functions execution 48 | * 49 | * > Remember: First rejection will throw an error in function without waiting for others to finish. 50 | * 51 | * This behavior usually is what is expected: Fails as soon as any request fails. 52 | */ 53 | // @playground-link 54 | const parallelAsyncMap = async () => { 55 | const data = [5, 7, 3, 10, 40]; 56 | 57 | const apiCall = (ms: number): Promise => 58 | new Promise((res) => { 59 | console.log(`-> ${ms}`); 60 | setTimeout(() => { 61 | console.log(`<- ${ms}`); 62 | res(ms * 100); 63 | }, ms); 64 | }); 65 | 66 | const asyncRes = await Promise.all( 67 | data.map(async (i) => { 68 | return await apiCall(i); 69 | }) 70 | ); 71 | console.log(asyncRes); 72 | }; 73 | parallelAsyncMap(); 74 | /** 75 | * Pay attention that `<-` are happening in parallel and returning in order (low to high). 76 | * But overall results array is built from corresponding results (not ordered) 77 | ``` 78 | -> 5 79 | -> 7 80 | -> 3 81 | -> 10 82 | -> 40 83 | <- 3 84 | <- 5 85 | <- 7 86 | <- 10 87 | <- 40 88 | [ 500, 700, 300, 1000, 4000 ] 89 | ``` 90 | */ 91 | 92 | /** 93 | * ## Async reduce 94 | * 95 | * Using async function in reduce. 96 | */ 97 | // @playground-link 98 | const sequentialData = [5, 10, 15, 95, 150]; 99 | 100 | async function sequentialAsyncReduce() { 101 | const apiCall = (ms: number): Promise => 102 | new Promise((res) => { 103 | console.log(`-> ${ms}`); 104 | setTimeout(() => { 105 | console.log(`<- ${ms}`); 106 | res(ms * 100); 107 | }, ms); 108 | }); 109 | 110 | const results = await sequentialData.reduce( 111 | async (acc: Promise, val: number) => { 112 | const results = await acc; 113 | await apiCall(val); 114 | return [...results, val]; 115 | }, 116 | Promise.resolve([]) 117 | ); 118 | console.log(`Results ${results}`); 119 | } 120 | sequentialAsyncReduce(); 121 | /** 122 | * Expected output: 123 | ``` 124 | -> 5 125 | <- 5 126 | -> 10 127 | <- 10 128 | -> 15 129 | <- 15 130 | -> 95 131 | <- 95 132 | -> 150 133 | <- 150 134 | Results 5,10,15,95,150 135 | ``` 136 | */ 137 | /** 138 | * Use `.map` and `.reduce` to apply async function over array elements in groups. 139 | */ 140 | // @playground-link 141 | const chunk = (input: number[], count = 10): number[][] => 142 | input.length > 0 143 | ? [input.slice(0, count), ...chunk(input.slice(count), count)] 144 | : []; 145 | 146 | async function parallelAsyncReduce() { 147 | const data = [5, 10, 15, 20, 25, 40, 50, 100, 120, 150, 100, 90, 95, 150]; 148 | 149 | const apiCall = (ms: number): Promise => 150 | new Promise((res) => { 151 | console.log(`-> ${ms}`); 152 | setTimeout(() => { 153 | console.log(`<- ${ms}`); 154 | res(ms * 100); 155 | }, ms); 156 | }); 157 | 158 | const mapChunks = ( 159 | localData: number[], 160 | cb: (i: number) => Promise, 161 | size = 5 162 | ): Promise => { 163 | const batches = chunk(localData, size); 164 | return batches.reduce(async (acc: Promise, batch: number[]) => { 165 | const arr = await acc; 166 | const thisBatch = await Promise.all(batch.map(cb)); 167 | return [...arr, ...thisBatch]; 168 | }, Promise.resolve([])); 169 | }; 170 | 171 | const result = await mapChunks(data, async (value: number) => { 172 | return await apiCall(value); 173 | }); 174 | console.log({ result }); 175 | } 176 | parallelAsyncReduce(); 177 | 178 | /** 179 | * Expected output: 180 | ``` 181 | -> 5 182 | -> 10 183 | -> 15 184 | -> 20 185 | -> 25 186 | <- 5 187 | <- 10 188 | <- 15 189 | <- 20 190 | <- 25 191 | 192 | -> 40 193 | -> 50 194 | -> 100 195 | -> 120 196 | -> 150 197 | <- 40 198 | <- 50 199 | <- 100 200 | <- 120 201 | <- 150 202 | 203 | -> 100 204 | -> 90 205 | -> 95 206 | -> 150 207 | <- 90 208 | <- 95 209 | <- 100 210 | <- 150 211 | { 212 | result: [ 213 | 500, 1000, 1500, 214 | 2000, 2500, 4000, 215 | 5000, 10000, 12000, 216 | 15000, 10000, 9000, 217 | 9500, 15000 218 | ] 219 | } 220 | ``` 221 | */ 222 | -------------------------------------------------------------------------------- /src/topics/errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ## Errors 3 | * 4 | * There are multiple strategies how to handle an error. 5 | * 6 | * It is always performed by `try-catch` statement. 7 | * 8 | * > Remember: Async function invoking will throw rejected value. It can be captured by try-catch 9 | * 10 | * [Read more at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) 11 | * 12 | * With a recent introduction of `unknown` type in a 'catch' block, value whould be first checked to be an error: `if (e instanceof Error) { ... }` 13 | * 14 | * ### Catch all 15 | */ 16 | // @playground-link 17 | function throwError() { 18 | try { 19 | throw new Error("My Error"); 20 | } catch (e: unknown) { 21 | console.log(e); 22 | } 23 | } 24 | throwError(); 25 | /** 26 | * ### Re-throwing errors 27 | * 28 | * In some cases you can expect some error, perform an action and throw the same error to be processed 29 | * on higher levels. 30 | */ 31 | // @playground-link 32 | function reThrow() { 33 | try { 34 | throw new Error("My Error"); 35 | } catch (e: unknown) { 36 | console.log(`Error happened: ${e}`); 37 | throw e; 38 | } 39 | } 40 | reThrow(); 41 | /** 42 | * ### Catching only subset 43 | * 44 | * You can filter out only necessary errors in `catch` using `instanceof` 45 | */ 46 | // @playground-link 47 | function filterErrorInstanceOf() { 48 | try { 49 | throw new TypeError("Bad Type"); 50 | } catch (e) { 51 | if (e instanceof TypeError) { 52 | console.log(`Expected type error: ${e}`); 53 | } else if (e instanceof RangeError) { 54 | console.log(`Expected range error: ${e}`); 55 | } else { 56 | // re-throw all others 57 | throw e; 58 | } 59 | } 60 | } 61 | filterErrorInstanceOf(); 62 | 63 | /** 64 | * Or as variation use `switch` case: 65 | */ 66 | // @playground-link 67 | function filterErrorSwitch() { 68 | try { 69 | throw new TypeError("Bad Type"); 70 | } catch (e: unknown) { 71 | if (e instanceof Error) { 72 | switch (e.constructor) { 73 | case TypeError: 74 | case RangeError: 75 | console.log(`Expected error: ${e}`); 76 | break; 77 | default: 78 | throw e; 79 | } 80 | } 81 | } 82 | } 83 | filterErrorSwitch(); 84 | 85 | /** 86 | * ### Use finally 87 | * 88 | * `finally` is called each time after `try` or `catch` been called, regardless of thrown exceptions 89 | */ 90 | function errorFinally() { 91 | try { 92 | throw new TypeError("Bad Type"); 93 | } catch (e) { 94 | if (e instanceof TypeError) { 95 | console.log(`Expected type error: ${e}`); 96 | throw e; 97 | } 98 | } finally { 99 | console.log("I will be called always"); 100 | } 101 | } 102 | errorFinally(); 103 | 104 | /** 105 | * ### Custom Errors 106 | * 107 | * Custom errors can be thrown as well. To be able to do it, just extend `Error` class, and throw newly created instance. 108 | * 109 | * > Remember: Do not `throw` anything except Error or it's subclasses 110 | */ 111 | // @playground-link 112 | class InvalidInputError extends Error { 113 | constructor(message: string) { 114 | super(message); 115 | this.name = "InvalidInputError"; 116 | this.stack = `${this.message}\n${new Error().stack}`; 117 | } 118 | } 119 | const invalidValue = 100; 120 | try { 121 | if (invalidValue > 0) { 122 | throw new InvalidInputError("Some field is not valid"); 123 | } 124 | } catch (e: unknown) { 125 | if (e instanceof Error) { 126 | console.log(e.name); // InvalidInputError 127 | console.log(e.message); // Some field is not valid 128 | console.log(e.stack); 129 | // Some field is not valid 130 | // Error 131 | // at new InvalidInputError (...) 132 | // at eval (...) 133 | console.log(`Error happened: ${e}`); // Error happened: InvalidInputError: Some field is not valid 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/topics/replace-class.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Use interface instead of class 3 | * 4 | * `class` definition can be replaced by function returning required object interface instance `DbAdapter`. 5 | * This is more popular approach compared to define new `class` and using `new` 6 | * 7 | * > Always try to use functional style ! 8 | * 9 | * Sample database adapter with few methods and single property 10 | */ 11 | interface DbAdapter { 12 | databaseName: string; 13 | put: (item: string) => void; 14 | get: (id: string) => string | undefined; 15 | } 16 | /** 17 | * Could be created inside of `buildDatabaseAdapter` which receive "constructor" parameter `databaseName` 18 | * Both functions `get` and `put` are exposed by interface definition as public. 19 | * 20 | * Within function you could define any other scoped functions, they will remain private. 21 | */ 22 | const buildDatabaseAdapter = (databaseName: string): DbAdapter => { 23 | const realDatabaseConnector = {} as any; 24 | 25 | // private 26 | const _getData = (id: string) => realDatabaseConnector.get(id); 27 | 28 | // exposed via return, it is public 29 | function get(id: string) { 30 | return _getData(id); 31 | } 32 | function put(item: string) { 33 | // no `this` required here 34 | return realDatabaseConnector.put(item); 35 | } 36 | 37 | return { 38 | databaseName, 39 | put, 40 | get, 41 | }; 42 | }; 43 | const myDbAdapter: DbAdapter = buildDatabaseAdapter("sample"); 44 | 45 | /** 46 | * Notable advantages: 47 | * - No need to use `new` to initialize instance of the class, just function call 48 | * - No need in constructor and reassigning parameters to class fields 49 | * - `this` usage issues disappear, all functions have access to scope variables 50 | */ 51 | -------------------------------------------------------------------------------- /src/topics/union-enum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # Using Enums 3 | * 4 | * When your emun is defined just to be supplied as parameter into some other functions. Continue using it. 5 | * 6 | */ 7 | // @playground-link 8 | enum MyEnumUserType { 9 | ADMIN, 10 | UNKNOWN, 11 | REGISTERED, 12 | } 13 | const processUserData = (data: any, userType: MyEnumUserType) => { 14 | if (userType === MyEnumUserType.ADMIN) { 15 | console.log(`Admin data:`, data); 16 | } else if (userType == MyEnumUserType.REGISTERED) { 17 | console.log(`Registered user data:`, data); 18 | } else { 19 | console.log(`Unknown data:`, data); 20 | } 21 | }; 22 | processUserData({ test: 1 }, MyEnumUserType.ADMIN); 23 | processUserData({ test: 2 }, MyEnumUserType.UNKNOWN); 24 | /** 25 | * In such situations it is usually created (hardcoded) and compared with the same enum values. 26 | * No types conversion happening 27 | * 28 | * ## Problems with enums 29 | * 30 | * Starts when enum values are converted. It is not a big deal to print values, you can assign string or number 31 | * to the corresponding key. But parsing string into enum looks "strange" 32 | * 33 | * Given a situation when `MyEnumUserType` is now read out of an input, usually some string. 34 | */ 35 | const myUserType: string = "ADMIN"; 36 | const myUserInEnumType: MyEnumUserType = 37 | MyEnumUserType[myUserType as keyof typeof MyEnumUserType]; 38 | processUserData({ test: 3 }, myUserInEnumType); 39 | /** 40 | * Now, the function can be called but `as keyof typeof` looks as not needed, but required. 41 | * 42 | * # When to use union types instead of enum 43 | * 44 | * `MyEnumUserType` defined with union type: 45 | */ 46 | type UnionUserType = "ADMIN" | "REGISTERED" | "UNKNOWN"; 47 | /** 48 | * This is not so convinent because those hardcoded string constants are set as strings everywhere. 49 | * 50 | * So here is upgraded and more flexible version: 51 | */ 52 | // @playground-link 53 | const TypeAdmin = "ADMIN"; 54 | const TypeUnknown = "UNKNOWN"; 55 | const TypeRegistered = "REGISTERED"; 56 | 57 | type UpgradedUserType = typeof TypeAdmin | typeof TypeAdmin | typeof TypeAdmin; 58 | 59 | const myUserTypeAgain: string = "ADMIN"; // just a string 60 | const adminAssignedDirectly: UpgradedUserType = "ADMIN"; // already enum value 61 | 62 | const exportUserType = (userType: UpgradedUserType) => console.log(userType); 63 | 64 | const parseUserType = (userType: string): UpgradedUserType => 65 | userType as UpgradedUserType; 66 | 67 | /** 68 | * # Summary 69 | * 70 | * Use `enum` when it is not leaving codebase. 71 | * 72 | * Use `union type` as union replacement when your keys are parsed from some input. 73 | */ 74 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 5 | "strict": true /* Enable all strict type-checking options. */, 6 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 7 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 8 | "skipLibCheck": true /* Skip type checking of declaration files. */, 9 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 10 | } 11 | } 12 | --------------------------------------------------------------------------------