├── .gitignore ├── 01-ecmascript-modules ├── 01-problem │ ├── index.html │ └── src │ │ ├── config.json │ │ ├── index.js │ │ ├── mock-api.json │ │ └── service.js ├── 02-esmodules │ ├── index.html │ └── src │ │ ├── config.json │ │ ├── index.js │ │ ├── mock-api.json │ │ └── service.js └── 03-shared-code+import-from-url │ ├── node │ └── index.mjs │ ├── shared │ └── currencyManager.mjs │ └── web │ ├── index.html │ └── index.mjs ├── 02-design-patterns ├── index.html ├── package-lock.json ├── package.json └── src │ ├── config.json │ ├── controller.js │ ├── index.js │ ├── mock-api.json │ ├── service.js │ └── view.js ├── 03-native-tests ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── config.json │ ├── controller.js │ ├── index.js │ ├── service.js │ └── view.js └── test │ └── index.test.js ├── 04-streams ├── app │ ├── index.html │ ├── index.js │ ├── package-lock.json │ └── package.json ├── package-lock.json ├── package.json └── server │ ├── animeflv.csv │ ├── index.js │ ├── package-lock.json │ └── package.json ├── 05-multithreading-in-the-browser ├── assets │ └── database-small.csv ├── index.html ├── package-lock.json ├── package.json └── src │ ├── controller.js │ ├── index.js │ ├── service.js │ ├── view.js │ └── worker.js ├── README.md └── annotations.txt /.gitignore: -------------------------------------------------------------------------------- 1 | database.csv 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | -------------------------------------------------------------------------------- /01-ecmascript-modules/01-problem/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /01-ecmascript-modules/01-problem/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "/src/mock-api.json" 3 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/01-problem/src/index.js: -------------------------------------------------------------------------------- 1 | // explicitamente esperar pela página carregar 2 | window.onload = async () => { 3 | const config = await (await fetch("/src/config.json")).json() 4 | // não dá para saber de onde veio 5 | const service = new window.Service({ 6 | url: config.url 7 | }) 8 | 9 | const chars = await service.getCharacters({ skip: 0, limit: 5 }) 10 | const boldIfSmith = (name) => /smith/i.test(name) ? `${name}` : name 11 | const htmlEl = chars 12 | .map(item => `
  1. ${boldIfSmith(item.name)}
  2. `) 13 | .join("
    ") 14 | 15 | const output = document.querySelector('#output') 16 | output.innerHTML = htmlEl 17 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/01-problem/src/mock-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "count": 826, 4 | "pages": 42, 5 | "next": "https://rickandmortyapi.com/api/character?page=2", 6 | "prev": null 7 | }, 8 | "results": [ 9 | { 10 | "id": 1, 11 | "name": "Rick Sanchez", 12 | "status": "Alive", 13 | "species": "Human", 14 | "type": "", 15 | "gender": "Male", 16 | "origin": { 17 | "name": "Earth (C-137)", 18 | "url": "https://rickandmortyapi.com/api/location/1" 19 | }, 20 | "location": { 21 | "name": "Citadel of Ricks", 22 | "url": "https://rickandmortyapi.com/api/location/3" 23 | }, 24 | "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg", 25 | "episode": [ 26 | "https://rickandmortyapi.com/api/episode/1", 27 | "https://rickandmortyapi.com/api/episode/2", 28 | "https://rickandmortyapi.com/api/episode/3", 29 | "https://rickandmortyapi.com/api/episode/4", 30 | "https://rickandmortyapi.com/api/episode/5", 31 | "https://rickandmortyapi.com/api/episode/6", 32 | "https://rickandmortyapi.com/api/episode/7", 33 | "https://rickandmortyapi.com/api/episode/8", 34 | "https://rickandmortyapi.com/api/episode/9", 35 | "https://rickandmortyapi.com/api/episode/10", 36 | "https://rickandmortyapi.com/api/episode/11", 37 | "https://rickandmortyapi.com/api/episode/12", 38 | "https://rickandmortyapi.com/api/episode/13", 39 | "https://rickandmortyapi.com/api/episode/14", 40 | "https://rickandmortyapi.com/api/episode/15", 41 | "https://rickandmortyapi.com/api/episode/16", 42 | "https://rickandmortyapi.com/api/episode/17", 43 | "https://rickandmortyapi.com/api/episode/18", 44 | "https://rickandmortyapi.com/api/episode/19", 45 | "https://rickandmortyapi.com/api/episode/20", 46 | "https://rickandmortyapi.com/api/episode/21", 47 | "https://rickandmortyapi.com/api/episode/22", 48 | "https://rickandmortyapi.com/api/episode/23", 49 | "https://rickandmortyapi.com/api/episode/24", 50 | "https://rickandmortyapi.com/api/episode/25", 51 | "https://rickandmortyapi.com/api/episode/26", 52 | "https://rickandmortyapi.com/api/episode/27", 53 | "https://rickandmortyapi.com/api/episode/28", 54 | "https://rickandmortyapi.com/api/episode/29", 55 | "https://rickandmortyapi.com/api/episode/30", 56 | "https://rickandmortyapi.com/api/episode/31", 57 | "https://rickandmortyapi.com/api/episode/32", 58 | "https://rickandmortyapi.com/api/episode/33", 59 | "https://rickandmortyapi.com/api/episode/34", 60 | "https://rickandmortyapi.com/api/episode/35", 61 | "https://rickandmortyapi.com/api/episode/36", 62 | "https://rickandmortyapi.com/api/episode/37", 63 | "https://rickandmortyapi.com/api/episode/38", 64 | "https://rickandmortyapi.com/api/episode/39", 65 | "https://rickandmortyapi.com/api/episode/40", 66 | "https://rickandmortyapi.com/api/episode/41", 67 | "https://rickandmortyapi.com/api/episode/42", 68 | "https://rickandmortyapi.com/api/episode/43", 69 | "https://rickandmortyapi.com/api/episode/44", 70 | "https://rickandmortyapi.com/api/episode/45", 71 | "https://rickandmortyapi.com/api/episode/46", 72 | "https://rickandmortyapi.com/api/episode/47", 73 | "https://rickandmortyapi.com/api/episode/48", 74 | "https://rickandmortyapi.com/api/episode/49", 75 | "https://rickandmortyapi.com/api/episode/50", 76 | "https://rickandmortyapi.com/api/episode/51" 77 | ], 78 | "url": "https://rickandmortyapi.com/api/character/1", 79 | "created": "2017-11-04T18:48:46.250Z" 80 | }, 81 | { 82 | "id": 2, 83 | "name": "Morty Smith", 84 | "status": "Alive", 85 | "species": "Human", 86 | "type": "", 87 | "gender": "Male", 88 | "origin": { 89 | "name": "unknown", 90 | "url": "" 91 | }, 92 | "location": { 93 | "name": "Citadel of Ricks", 94 | "url": "https://rickandmortyapi.com/api/location/3" 95 | }, 96 | "image": "https://rickandmortyapi.com/api/character/avatar/2.jpeg", 97 | "episode": [ 98 | "https://rickandmortyapi.com/api/episode/1", 99 | "https://rickandmortyapi.com/api/episode/2", 100 | "https://rickandmortyapi.com/api/episode/3", 101 | "https://rickandmortyapi.com/api/episode/4", 102 | "https://rickandmortyapi.com/api/episode/5", 103 | "https://rickandmortyapi.com/api/episode/6", 104 | "https://rickandmortyapi.com/api/episode/7", 105 | "https://rickandmortyapi.com/api/episode/8", 106 | "https://rickandmortyapi.com/api/episode/9", 107 | "https://rickandmortyapi.com/api/episode/10", 108 | "https://rickandmortyapi.com/api/episode/11", 109 | "https://rickandmortyapi.com/api/episode/12", 110 | "https://rickandmortyapi.com/api/episode/13", 111 | "https://rickandmortyapi.com/api/episode/14", 112 | "https://rickandmortyapi.com/api/episode/15", 113 | "https://rickandmortyapi.com/api/episode/16", 114 | "https://rickandmortyapi.com/api/episode/17", 115 | "https://rickandmortyapi.com/api/episode/18", 116 | "https://rickandmortyapi.com/api/episode/19", 117 | "https://rickandmortyapi.com/api/episode/20", 118 | "https://rickandmortyapi.com/api/episode/21", 119 | "https://rickandmortyapi.com/api/episode/22", 120 | "https://rickandmortyapi.com/api/episode/23", 121 | "https://rickandmortyapi.com/api/episode/24", 122 | "https://rickandmortyapi.com/api/episode/25", 123 | "https://rickandmortyapi.com/api/episode/26", 124 | "https://rickandmortyapi.com/api/episode/27", 125 | "https://rickandmortyapi.com/api/episode/28", 126 | "https://rickandmortyapi.com/api/episode/29", 127 | "https://rickandmortyapi.com/api/episode/30", 128 | "https://rickandmortyapi.com/api/episode/31", 129 | "https://rickandmortyapi.com/api/episode/32", 130 | "https://rickandmortyapi.com/api/episode/33", 131 | "https://rickandmortyapi.com/api/episode/34", 132 | "https://rickandmortyapi.com/api/episode/35", 133 | "https://rickandmortyapi.com/api/episode/36", 134 | "https://rickandmortyapi.com/api/episode/37", 135 | "https://rickandmortyapi.com/api/episode/38", 136 | "https://rickandmortyapi.com/api/episode/39", 137 | "https://rickandmortyapi.com/api/episode/40", 138 | "https://rickandmortyapi.com/api/episode/41", 139 | "https://rickandmortyapi.com/api/episode/42", 140 | "https://rickandmortyapi.com/api/episode/43", 141 | "https://rickandmortyapi.com/api/episode/44", 142 | "https://rickandmortyapi.com/api/episode/45", 143 | "https://rickandmortyapi.com/api/episode/46", 144 | "https://rickandmortyapi.com/api/episode/47", 145 | "https://rickandmortyapi.com/api/episode/48", 146 | "https://rickandmortyapi.com/api/episode/49", 147 | "https://rickandmortyapi.com/api/episode/50", 148 | "https://rickandmortyapi.com/api/episode/51" 149 | ], 150 | "url": "https://rickandmortyapi.com/api/character/2", 151 | "created": "2017-11-04T18:50:21.651Z" 152 | }, 153 | { 154 | "id": 3, 155 | "name": "Summer Smith", 156 | "status": "Alive", 157 | "species": "Human", 158 | "type": "", 159 | "gender": "Female", 160 | "origin": { 161 | "name": "Earth (Replacement Dimension)", 162 | "url": "https://rickandmortyapi.com/api/location/20" 163 | }, 164 | "location": { 165 | "name": "Earth (Replacement Dimension)", 166 | "url": "https://rickandmortyapi.com/api/location/20" 167 | }, 168 | "image": "https://rickandmortyapi.com/api/character/avatar/3.jpeg", 169 | "episode": [ 170 | "https://rickandmortyapi.com/api/episode/6", 171 | "https://rickandmortyapi.com/api/episode/7", 172 | "https://rickandmortyapi.com/api/episode/8", 173 | "https://rickandmortyapi.com/api/episode/9", 174 | "https://rickandmortyapi.com/api/episode/10", 175 | "https://rickandmortyapi.com/api/episode/11", 176 | "https://rickandmortyapi.com/api/episode/12", 177 | "https://rickandmortyapi.com/api/episode/14", 178 | "https://rickandmortyapi.com/api/episode/15", 179 | "https://rickandmortyapi.com/api/episode/16", 180 | "https://rickandmortyapi.com/api/episode/17", 181 | "https://rickandmortyapi.com/api/episode/18", 182 | "https://rickandmortyapi.com/api/episode/19", 183 | "https://rickandmortyapi.com/api/episode/20", 184 | "https://rickandmortyapi.com/api/episode/21", 185 | "https://rickandmortyapi.com/api/episode/22", 186 | "https://rickandmortyapi.com/api/episode/23", 187 | "https://rickandmortyapi.com/api/episode/24", 188 | "https://rickandmortyapi.com/api/episode/25", 189 | "https://rickandmortyapi.com/api/episode/26", 190 | "https://rickandmortyapi.com/api/episode/27", 191 | "https://rickandmortyapi.com/api/episode/29", 192 | "https://rickandmortyapi.com/api/episode/30", 193 | "https://rickandmortyapi.com/api/episode/31", 194 | "https://rickandmortyapi.com/api/episode/32", 195 | "https://rickandmortyapi.com/api/episode/33", 196 | "https://rickandmortyapi.com/api/episode/34", 197 | "https://rickandmortyapi.com/api/episode/35", 198 | "https://rickandmortyapi.com/api/episode/36", 199 | "https://rickandmortyapi.com/api/episode/38", 200 | "https://rickandmortyapi.com/api/episode/39", 201 | "https://rickandmortyapi.com/api/episode/40", 202 | "https://rickandmortyapi.com/api/episode/41", 203 | "https://rickandmortyapi.com/api/episode/42", 204 | "https://rickandmortyapi.com/api/episode/43", 205 | "https://rickandmortyapi.com/api/episode/44", 206 | "https://rickandmortyapi.com/api/episode/45", 207 | "https://rickandmortyapi.com/api/episode/46", 208 | "https://rickandmortyapi.com/api/episode/47", 209 | "https://rickandmortyapi.com/api/episode/48", 210 | "https://rickandmortyapi.com/api/episode/49", 211 | "https://rickandmortyapi.com/api/episode/51" 212 | ], 213 | "url": "https://rickandmortyapi.com/api/character/3", 214 | "created": "2017-11-04T19:09:56.428Z" 215 | }, 216 | { 217 | "id": 4, 218 | "name": "Beth Smith", 219 | "status": "Alive", 220 | "species": "Human", 221 | "type": "", 222 | "gender": "Female", 223 | "origin": { 224 | "name": "Earth (Replacement Dimension)", 225 | "url": "https://rickandmortyapi.com/api/location/20" 226 | }, 227 | "location": { 228 | "name": "Earth (Replacement Dimension)", 229 | "url": "https://rickandmortyapi.com/api/location/20" 230 | }, 231 | "image": "https://rickandmortyapi.com/api/character/avatar/4.jpeg", 232 | "episode": [ 233 | "https://rickandmortyapi.com/api/episode/6", 234 | "https://rickandmortyapi.com/api/episode/7", 235 | "https://rickandmortyapi.com/api/episode/8", 236 | "https://rickandmortyapi.com/api/episode/9", 237 | "https://rickandmortyapi.com/api/episode/10", 238 | "https://rickandmortyapi.com/api/episode/11", 239 | "https://rickandmortyapi.com/api/episode/12", 240 | "https://rickandmortyapi.com/api/episode/14", 241 | "https://rickandmortyapi.com/api/episode/15", 242 | "https://rickandmortyapi.com/api/episode/16", 243 | "https://rickandmortyapi.com/api/episode/18", 244 | "https://rickandmortyapi.com/api/episode/19", 245 | "https://rickandmortyapi.com/api/episode/20", 246 | "https://rickandmortyapi.com/api/episode/21", 247 | "https://rickandmortyapi.com/api/episode/22", 248 | "https://rickandmortyapi.com/api/episode/23", 249 | "https://rickandmortyapi.com/api/episode/24", 250 | "https://rickandmortyapi.com/api/episode/25", 251 | "https://rickandmortyapi.com/api/episode/26", 252 | "https://rickandmortyapi.com/api/episode/27", 253 | "https://rickandmortyapi.com/api/episode/28", 254 | "https://rickandmortyapi.com/api/episode/29", 255 | "https://rickandmortyapi.com/api/episode/30", 256 | "https://rickandmortyapi.com/api/episode/31", 257 | "https://rickandmortyapi.com/api/episode/32", 258 | "https://rickandmortyapi.com/api/episode/33", 259 | "https://rickandmortyapi.com/api/episode/34", 260 | "https://rickandmortyapi.com/api/episode/35", 261 | "https://rickandmortyapi.com/api/episode/36", 262 | "https://rickandmortyapi.com/api/episode/38", 263 | "https://rickandmortyapi.com/api/episode/39", 264 | "https://rickandmortyapi.com/api/episode/40", 265 | "https://rickandmortyapi.com/api/episode/41", 266 | "https://rickandmortyapi.com/api/episode/42", 267 | "https://rickandmortyapi.com/api/episode/43", 268 | "https://rickandmortyapi.com/api/episode/44", 269 | "https://rickandmortyapi.com/api/episode/45", 270 | "https://rickandmortyapi.com/api/episode/46", 271 | "https://rickandmortyapi.com/api/episode/47", 272 | "https://rickandmortyapi.com/api/episode/48", 273 | "https://rickandmortyapi.com/api/episode/49", 274 | "https://rickandmortyapi.com/api/episode/51" 275 | ], 276 | "url": "https://rickandmortyapi.com/api/character/4", 277 | "created": "2017-11-04T19:22:43.665Z" 278 | }, 279 | { 280 | "id": 5, 281 | "name": "Jerry Smith", 282 | "status": "Alive", 283 | "species": "Human", 284 | "type": "", 285 | "gender": "Male", 286 | "origin": { 287 | "name": "Earth (Replacement Dimension)", 288 | "url": "https://rickandmortyapi.com/api/location/20" 289 | }, 290 | "location": { 291 | "name": "Earth (Replacement Dimension)", 292 | "url": "https://rickandmortyapi.com/api/location/20" 293 | }, 294 | "image": "https://rickandmortyapi.com/api/character/avatar/5.jpeg", 295 | "episode": [ 296 | "https://rickandmortyapi.com/api/episode/6", 297 | "https://rickandmortyapi.com/api/episode/7", 298 | "https://rickandmortyapi.com/api/episode/8", 299 | "https://rickandmortyapi.com/api/episode/9", 300 | "https://rickandmortyapi.com/api/episode/10", 301 | "https://rickandmortyapi.com/api/episode/11", 302 | "https://rickandmortyapi.com/api/episode/12", 303 | "https://rickandmortyapi.com/api/episode/13", 304 | "https://rickandmortyapi.com/api/episode/14", 305 | "https://rickandmortyapi.com/api/episode/15", 306 | "https://rickandmortyapi.com/api/episode/16", 307 | "https://rickandmortyapi.com/api/episode/18", 308 | "https://rickandmortyapi.com/api/episode/19", 309 | "https://rickandmortyapi.com/api/episode/20", 310 | "https://rickandmortyapi.com/api/episode/21", 311 | "https://rickandmortyapi.com/api/episode/22", 312 | "https://rickandmortyapi.com/api/episode/23", 313 | "https://rickandmortyapi.com/api/episode/26", 314 | "https://rickandmortyapi.com/api/episode/29", 315 | "https://rickandmortyapi.com/api/episode/30", 316 | "https://rickandmortyapi.com/api/episode/31", 317 | "https://rickandmortyapi.com/api/episode/32", 318 | "https://rickandmortyapi.com/api/episode/33", 319 | "https://rickandmortyapi.com/api/episode/35", 320 | "https://rickandmortyapi.com/api/episode/36", 321 | "https://rickandmortyapi.com/api/episode/38", 322 | "https://rickandmortyapi.com/api/episode/39", 323 | "https://rickandmortyapi.com/api/episode/40", 324 | "https://rickandmortyapi.com/api/episode/41", 325 | "https://rickandmortyapi.com/api/episode/42", 326 | "https://rickandmortyapi.com/api/episode/43", 327 | "https://rickandmortyapi.com/api/episode/44", 328 | "https://rickandmortyapi.com/api/episode/45", 329 | "https://rickandmortyapi.com/api/episode/46", 330 | "https://rickandmortyapi.com/api/episode/47", 331 | "https://rickandmortyapi.com/api/episode/48", 332 | "https://rickandmortyapi.com/api/episode/49", 333 | "https://rickandmortyapi.com/api/episode/50", 334 | "https://rickandmortyapi.com/api/episode/51" 335 | ], 336 | "url": "https://rickandmortyapi.com/api/character/5", 337 | "created": "2017-11-04T19:26:56.301Z" 338 | }, 339 | { 340 | "id": 6, 341 | "name": "Abadango Cluster Princess", 342 | "status": "Alive", 343 | "species": "Alien", 344 | "type": "", 345 | "gender": "Female", 346 | "origin": { 347 | "name": "Abadango", 348 | "url": "https://rickandmortyapi.com/api/location/2" 349 | }, 350 | "location": { 351 | "name": "Abadango", 352 | "url": "https://rickandmortyapi.com/api/location/2" 353 | }, 354 | "image": "https://rickandmortyapi.com/api/character/avatar/6.jpeg", 355 | "episode": [ 356 | "https://rickandmortyapi.com/api/episode/27" 357 | ], 358 | "url": "https://rickandmortyapi.com/api/character/6", 359 | "created": "2017-11-04T19:50:28.250Z" 360 | }, 361 | { 362 | "id": 7, 363 | "name": "Abradolf Lincler", 364 | "status": "unknown", 365 | "species": "Human", 366 | "type": "Genetic experiment", 367 | "gender": "Male", 368 | "origin": { 369 | "name": "Earth (Replacement Dimension)", 370 | "url": "https://rickandmortyapi.com/api/location/20" 371 | }, 372 | "location": { 373 | "name": "Testicle Monster Dimension", 374 | "url": "https://rickandmortyapi.com/api/location/21" 375 | }, 376 | "image": "https://rickandmortyapi.com/api/character/avatar/7.jpeg", 377 | "episode": [ 378 | "https://rickandmortyapi.com/api/episode/10", 379 | "https://rickandmortyapi.com/api/episode/11" 380 | ], 381 | "url": "https://rickandmortyapi.com/api/character/7", 382 | "created": "2017-11-04T19:59:20.523Z" 383 | }, 384 | { 385 | "id": 8, 386 | "name": "Adjudicator Rick", 387 | "status": "Dead", 388 | "species": "Human", 389 | "type": "", 390 | "gender": "Male", 391 | "origin": { 392 | "name": "unknown", 393 | "url": "" 394 | }, 395 | "location": { 396 | "name": "Citadel of Ricks", 397 | "url": "https://rickandmortyapi.com/api/location/3" 398 | }, 399 | "image": "https://rickandmortyapi.com/api/character/avatar/8.jpeg", 400 | "episode": [ 401 | "https://rickandmortyapi.com/api/episode/28" 402 | ], 403 | "url": "https://rickandmortyapi.com/api/character/8", 404 | "created": "2017-11-04T20:03:34.737Z" 405 | }, 406 | { 407 | "id": 9, 408 | "name": "Agency Director", 409 | "status": "Dead", 410 | "species": "Human", 411 | "type": "", 412 | "gender": "Male", 413 | "origin": { 414 | "name": "Earth (Replacement Dimension)", 415 | "url": "https://rickandmortyapi.com/api/location/20" 416 | }, 417 | "location": { 418 | "name": "Earth (Replacement Dimension)", 419 | "url": "https://rickandmortyapi.com/api/location/20" 420 | }, 421 | "image": "https://rickandmortyapi.com/api/character/avatar/9.jpeg", 422 | "episode": [ 423 | "https://rickandmortyapi.com/api/episode/24" 424 | ], 425 | "url": "https://rickandmortyapi.com/api/character/9", 426 | "created": "2017-11-04T20:06:54.976Z" 427 | }, 428 | { 429 | "id": 10, 430 | "name": "Alan Rails", 431 | "status": "Dead", 432 | "species": "Human", 433 | "type": "Superhuman (Ghost trains summoner)", 434 | "gender": "Male", 435 | "origin": { 436 | "name": "unknown", 437 | "url": "" 438 | }, 439 | "location": { 440 | "name": "Worldender's lair", 441 | "url": "https://rickandmortyapi.com/api/location/4" 442 | }, 443 | "image": "https://rickandmortyapi.com/api/character/avatar/10.jpeg", 444 | "episode": [ 445 | "https://rickandmortyapi.com/api/episode/25" 446 | ], 447 | "url": "https://rickandmortyapi.com/api/character/10", 448 | "created": "2017-11-04T20:19:09.017Z" 449 | }, 450 | { 451 | "id": 11, 452 | "name": "Albert Einstein", 453 | "status": "Dead", 454 | "species": "Human", 455 | "type": "", 456 | "gender": "Male", 457 | "origin": { 458 | "name": "Earth (C-137)", 459 | "url": "https://rickandmortyapi.com/api/location/1" 460 | }, 461 | "location": { 462 | "name": "Earth (Replacement Dimension)", 463 | "url": "https://rickandmortyapi.com/api/location/20" 464 | }, 465 | "image": "https://rickandmortyapi.com/api/character/avatar/11.jpeg", 466 | "episode": [ 467 | "https://rickandmortyapi.com/api/episode/12" 468 | ], 469 | "url": "https://rickandmortyapi.com/api/character/11", 470 | "created": "2017-11-04T20:20:20.965Z" 471 | }, 472 | { 473 | "id": 12, 474 | "name": "Alexander", 475 | "status": "Dead", 476 | "species": "Human", 477 | "type": "", 478 | "gender": "Male", 479 | "origin": { 480 | "name": "Earth (C-137)", 481 | "url": "https://rickandmortyapi.com/api/location/1" 482 | }, 483 | "location": { 484 | "name": "Anatomy Park", 485 | "url": "https://rickandmortyapi.com/api/location/5" 486 | }, 487 | "image": "https://rickandmortyapi.com/api/character/avatar/12.jpeg", 488 | "episode": [ 489 | "https://rickandmortyapi.com/api/episode/3" 490 | ], 491 | "url": "https://rickandmortyapi.com/api/character/12", 492 | "created": "2017-11-04T20:32:33.144Z" 493 | }, 494 | { 495 | "id": 13, 496 | "name": "Alien Googah", 497 | "status": "unknown", 498 | "species": "Alien", 499 | "type": "", 500 | "gender": "unknown", 501 | "origin": { 502 | "name": "unknown", 503 | "url": "" 504 | }, 505 | "location": { 506 | "name": "Earth (Replacement Dimension)", 507 | "url": "https://rickandmortyapi.com/api/location/20" 508 | }, 509 | "image": "https://rickandmortyapi.com/api/character/avatar/13.jpeg", 510 | "episode": [ 511 | "https://rickandmortyapi.com/api/episode/31" 512 | ], 513 | "url": "https://rickandmortyapi.com/api/character/13", 514 | "created": "2017-11-04T20:33:30.779Z" 515 | }, 516 | { 517 | "id": 14, 518 | "name": "Alien Morty", 519 | "status": "unknown", 520 | "species": "Alien", 521 | "type": "", 522 | "gender": "Male", 523 | "origin": { 524 | "name": "unknown", 525 | "url": "" 526 | }, 527 | "location": { 528 | "name": "Citadel of Ricks", 529 | "url": "https://rickandmortyapi.com/api/location/3" 530 | }, 531 | "image": "https://rickandmortyapi.com/api/character/avatar/14.jpeg", 532 | "episode": [ 533 | "https://rickandmortyapi.com/api/episode/10" 534 | ], 535 | "url": "https://rickandmortyapi.com/api/character/14", 536 | "created": "2017-11-04T20:51:31.373Z" 537 | }, 538 | { 539 | "id": 15, 540 | "name": "Alien Rick", 541 | "status": "unknown", 542 | "species": "Alien", 543 | "type": "", 544 | "gender": "Male", 545 | "origin": { 546 | "name": "unknown", 547 | "url": "" 548 | }, 549 | "location": { 550 | "name": "Citadel of Ricks", 551 | "url": "https://rickandmortyapi.com/api/location/3" 552 | }, 553 | "image": "https://rickandmortyapi.com/api/character/avatar/15.jpeg", 554 | "episode": [ 555 | "https://rickandmortyapi.com/api/episode/10" 556 | ], 557 | "url": "https://rickandmortyapi.com/api/character/15", 558 | "created": "2017-11-04T20:56:13.215Z" 559 | }, 560 | { 561 | "id": 16, 562 | "name": "Amish Cyborg", 563 | "status": "Dead", 564 | "species": "Alien", 565 | "type": "Parasite", 566 | "gender": "Male", 567 | "origin": { 568 | "name": "unknown", 569 | "url": "" 570 | }, 571 | "location": { 572 | "name": "Earth (Replacement Dimension)", 573 | "url": "https://rickandmortyapi.com/api/location/20" 574 | }, 575 | "image": "https://rickandmortyapi.com/api/character/avatar/16.jpeg", 576 | "episode": [ 577 | "https://rickandmortyapi.com/api/episode/15" 578 | ], 579 | "url": "https://rickandmortyapi.com/api/character/16", 580 | "created": "2017-11-04T21:12:45.235Z" 581 | }, 582 | { 583 | "id": 17, 584 | "name": "Annie", 585 | "status": "Alive", 586 | "species": "Human", 587 | "type": "", 588 | "gender": "Female", 589 | "origin": { 590 | "name": "Earth (C-137)", 591 | "url": "https://rickandmortyapi.com/api/location/1" 592 | }, 593 | "location": { 594 | "name": "Anatomy Park", 595 | "url": "https://rickandmortyapi.com/api/location/5" 596 | }, 597 | "image": "https://rickandmortyapi.com/api/character/avatar/17.jpeg", 598 | "episode": [ 599 | "https://rickandmortyapi.com/api/episode/3" 600 | ], 601 | "url": "https://rickandmortyapi.com/api/character/17", 602 | "created": "2017-11-04T22:21:24.481Z" 603 | }, 604 | { 605 | "id": 18, 606 | "name": "Antenna Morty", 607 | "status": "Alive", 608 | "species": "Human", 609 | "type": "Human with antennae", 610 | "gender": "Male", 611 | "origin": { 612 | "name": "unknown", 613 | "url": "" 614 | }, 615 | "location": { 616 | "name": "Citadel of Ricks", 617 | "url": "https://rickandmortyapi.com/api/location/3" 618 | }, 619 | "image": "https://rickandmortyapi.com/api/character/avatar/18.jpeg", 620 | "episode": [ 621 | "https://rickandmortyapi.com/api/episode/10", 622 | "https://rickandmortyapi.com/api/episode/28" 623 | ], 624 | "url": "https://rickandmortyapi.com/api/character/18", 625 | "created": "2017-11-04T22:25:29.008Z" 626 | }, 627 | { 628 | "id": 19, 629 | "name": "Antenna Rick", 630 | "status": "unknown", 631 | "species": "Human", 632 | "type": "Human with antennae", 633 | "gender": "Male", 634 | "origin": { 635 | "name": "unknown", 636 | "url": "" 637 | }, 638 | "location": { 639 | "name": "unknown", 640 | "url": "" 641 | }, 642 | "image": "https://rickandmortyapi.com/api/character/avatar/19.jpeg", 643 | "episode": [ 644 | "https://rickandmortyapi.com/api/episode/10" 645 | ], 646 | "url": "https://rickandmortyapi.com/api/character/19", 647 | "created": "2017-11-04T22:28:13.756Z" 648 | }, 649 | { 650 | "id": 20, 651 | "name": "Ants in my Eyes Johnson", 652 | "status": "unknown", 653 | "species": "Human", 654 | "type": "Human with ants in his eyes", 655 | "gender": "Male", 656 | "origin": { 657 | "name": "unknown", 658 | "url": "" 659 | }, 660 | "location": { 661 | "name": "Interdimensional Cable", 662 | "url": "https://rickandmortyapi.com/api/location/6" 663 | }, 664 | "image": "https://rickandmortyapi.com/api/character/avatar/20.jpeg", 665 | "episode": [ 666 | "https://rickandmortyapi.com/api/episode/8" 667 | ], 668 | "url": "https://rickandmortyapi.com/api/character/20", 669 | "created": "2017-11-04T22:34:53.659Z" 670 | } 671 | ] 672 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/01-problem/src/service.js: -------------------------------------------------------------------------------- 1 | class Service { 2 | constructor({ url }) { 3 | this.url = url 4 | } 5 | async getCharacters({ skip, limit }) { 6 | const data = (await (await fetch(this.url)).json()).results 7 | 8 | return data.slice(skip, limit) 9 | } 10 | } 11 | window.Service = Service -------------------------------------------------------------------------------- /01-ecmascript-modules/02-esmodules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
      11 | 12 | 13 | -------------------------------------------------------------------------------- /01-ecmascript-modules/02-esmodules/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "/src/mock-api.json" 3 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/02-esmodules/src/index.js: -------------------------------------------------------------------------------- 1 | import config from './config.json' assert { type: "json"} 2 | import Service from './service.js' 3 | 4 | const service = new Service({ 5 | url: config.url 6 | }) 7 | 8 | const chars = await service.getCharacters({ skip: 0, limit: 5 }) 9 | const boldIfSmith = (name) => /smith/i.test(name) ? `${name}` : name 10 | const htmlEl = chars 11 | .map(item => `
    1. ${boldIfSmith(item.name)}
    2. `) 12 | .join("
      ") 13 | 14 | const output = document.querySelector('#output') 15 | output.innerHTML = htmlEl -------------------------------------------------------------------------------- /01-ecmascript-modules/02-esmodules/src/mock-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "count": 826, 4 | "pages": 42, 5 | "next": "https://rickandmortyapi.com/api/character?page=2", 6 | "prev": null 7 | }, 8 | "results": [ 9 | { 10 | "id": 1, 11 | "name": "Rick Sanchez", 12 | "status": "Alive", 13 | "species": "Human", 14 | "type": "", 15 | "gender": "Male", 16 | "origin": { 17 | "name": "Earth (C-137)", 18 | "url": "https://rickandmortyapi.com/api/location/1" 19 | }, 20 | "location": { 21 | "name": "Citadel of Ricks", 22 | "url": "https://rickandmortyapi.com/api/location/3" 23 | }, 24 | "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg", 25 | "episode": [ 26 | "https://rickandmortyapi.com/api/episode/1", 27 | "https://rickandmortyapi.com/api/episode/2", 28 | "https://rickandmortyapi.com/api/episode/3", 29 | "https://rickandmortyapi.com/api/episode/4", 30 | "https://rickandmortyapi.com/api/episode/5", 31 | "https://rickandmortyapi.com/api/episode/6", 32 | "https://rickandmortyapi.com/api/episode/7", 33 | "https://rickandmortyapi.com/api/episode/8", 34 | "https://rickandmortyapi.com/api/episode/9", 35 | "https://rickandmortyapi.com/api/episode/10", 36 | "https://rickandmortyapi.com/api/episode/11", 37 | "https://rickandmortyapi.com/api/episode/12", 38 | "https://rickandmortyapi.com/api/episode/13", 39 | "https://rickandmortyapi.com/api/episode/14", 40 | "https://rickandmortyapi.com/api/episode/15", 41 | "https://rickandmortyapi.com/api/episode/16", 42 | "https://rickandmortyapi.com/api/episode/17", 43 | "https://rickandmortyapi.com/api/episode/18", 44 | "https://rickandmortyapi.com/api/episode/19", 45 | "https://rickandmortyapi.com/api/episode/20", 46 | "https://rickandmortyapi.com/api/episode/21", 47 | "https://rickandmortyapi.com/api/episode/22", 48 | "https://rickandmortyapi.com/api/episode/23", 49 | "https://rickandmortyapi.com/api/episode/24", 50 | "https://rickandmortyapi.com/api/episode/25", 51 | "https://rickandmortyapi.com/api/episode/26", 52 | "https://rickandmortyapi.com/api/episode/27", 53 | "https://rickandmortyapi.com/api/episode/28", 54 | "https://rickandmortyapi.com/api/episode/29", 55 | "https://rickandmortyapi.com/api/episode/30", 56 | "https://rickandmortyapi.com/api/episode/31", 57 | "https://rickandmortyapi.com/api/episode/32", 58 | "https://rickandmortyapi.com/api/episode/33", 59 | "https://rickandmortyapi.com/api/episode/34", 60 | "https://rickandmortyapi.com/api/episode/35", 61 | "https://rickandmortyapi.com/api/episode/36", 62 | "https://rickandmortyapi.com/api/episode/37", 63 | "https://rickandmortyapi.com/api/episode/38", 64 | "https://rickandmortyapi.com/api/episode/39", 65 | "https://rickandmortyapi.com/api/episode/40", 66 | "https://rickandmortyapi.com/api/episode/41", 67 | "https://rickandmortyapi.com/api/episode/42", 68 | "https://rickandmortyapi.com/api/episode/43", 69 | "https://rickandmortyapi.com/api/episode/44", 70 | "https://rickandmortyapi.com/api/episode/45", 71 | "https://rickandmortyapi.com/api/episode/46", 72 | "https://rickandmortyapi.com/api/episode/47", 73 | "https://rickandmortyapi.com/api/episode/48", 74 | "https://rickandmortyapi.com/api/episode/49", 75 | "https://rickandmortyapi.com/api/episode/50", 76 | "https://rickandmortyapi.com/api/episode/51" 77 | ], 78 | "url": "https://rickandmortyapi.com/api/character/1", 79 | "created": "2017-11-04T18:48:46.250Z" 80 | }, 81 | { 82 | "id": 2, 83 | "name": "Morty Smith", 84 | "status": "Alive", 85 | "species": "Human", 86 | "type": "", 87 | "gender": "Male", 88 | "origin": { 89 | "name": "unknown", 90 | "url": "" 91 | }, 92 | "location": { 93 | "name": "Citadel of Ricks", 94 | "url": "https://rickandmortyapi.com/api/location/3" 95 | }, 96 | "image": "https://rickandmortyapi.com/api/character/avatar/2.jpeg", 97 | "episode": [ 98 | "https://rickandmortyapi.com/api/episode/1", 99 | "https://rickandmortyapi.com/api/episode/2", 100 | "https://rickandmortyapi.com/api/episode/3", 101 | "https://rickandmortyapi.com/api/episode/4", 102 | "https://rickandmortyapi.com/api/episode/5", 103 | "https://rickandmortyapi.com/api/episode/6", 104 | "https://rickandmortyapi.com/api/episode/7", 105 | "https://rickandmortyapi.com/api/episode/8", 106 | "https://rickandmortyapi.com/api/episode/9", 107 | "https://rickandmortyapi.com/api/episode/10", 108 | "https://rickandmortyapi.com/api/episode/11", 109 | "https://rickandmortyapi.com/api/episode/12", 110 | "https://rickandmortyapi.com/api/episode/13", 111 | "https://rickandmortyapi.com/api/episode/14", 112 | "https://rickandmortyapi.com/api/episode/15", 113 | "https://rickandmortyapi.com/api/episode/16", 114 | "https://rickandmortyapi.com/api/episode/17", 115 | "https://rickandmortyapi.com/api/episode/18", 116 | "https://rickandmortyapi.com/api/episode/19", 117 | "https://rickandmortyapi.com/api/episode/20", 118 | "https://rickandmortyapi.com/api/episode/21", 119 | "https://rickandmortyapi.com/api/episode/22", 120 | "https://rickandmortyapi.com/api/episode/23", 121 | "https://rickandmortyapi.com/api/episode/24", 122 | "https://rickandmortyapi.com/api/episode/25", 123 | "https://rickandmortyapi.com/api/episode/26", 124 | "https://rickandmortyapi.com/api/episode/27", 125 | "https://rickandmortyapi.com/api/episode/28", 126 | "https://rickandmortyapi.com/api/episode/29", 127 | "https://rickandmortyapi.com/api/episode/30", 128 | "https://rickandmortyapi.com/api/episode/31", 129 | "https://rickandmortyapi.com/api/episode/32", 130 | "https://rickandmortyapi.com/api/episode/33", 131 | "https://rickandmortyapi.com/api/episode/34", 132 | "https://rickandmortyapi.com/api/episode/35", 133 | "https://rickandmortyapi.com/api/episode/36", 134 | "https://rickandmortyapi.com/api/episode/37", 135 | "https://rickandmortyapi.com/api/episode/38", 136 | "https://rickandmortyapi.com/api/episode/39", 137 | "https://rickandmortyapi.com/api/episode/40", 138 | "https://rickandmortyapi.com/api/episode/41", 139 | "https://rickandmortyapi.com/api/episode/42", 140 | "https://rickandmortyapi.com/api/episode/43", 141 | "https://rickandmortyapi.com/api/episode/44", 142 | "https://rickandmortyapi.com/api/episode/45", 143 | "https://rickandmortyapi.com/api/episode/46", 144 | "https://rickandmortyapi.com/api/episode/47", 145 | "https://rickandmortyapi.com/api/episode/48", 146 | "https://rickandmortyapi.com/api/episode/49", 147 | "https://rickandmortyapi.com/api/episode/50", 148 | "https://rickandmortyapi.com/api/episode/51" 149 | ], 150 | "url": "https://rickandmortyapi.com/api/character/2", 151 | "created": "2017-11-04T18:50:21.651Z" 152 | }, 153 | { 154 | "id": 3, 155 | "name": "Summer Smith", 156 | "status": "Alive", 157 | "species": "Human", 158 | "type": "", 159 | "gender": "Female", 160 | "origin": { 161 | "name": "Earth (Replacement Dimension)", 162 | "url": "https://rickandmortyapi.com/api/location/20" 163 | }, 164 | "location": { 165 | "name": "Earth (Replacement Dimension)", 166 | "url": "https://rickandmortyapi.com/api/location/20" 167 | }, 168 | "image": "https://rickandmortyapi.com/api/character/avatar/3.jpeg", 169 | "episode": [ 170 | "https://rickandmortyapi.com/api/episode/6", 171 | "https://rickandmortyapi.com/api/episode/7", 172 | "https://rickandmortyapi.com/api/episode/8", 173 | "https://rickandmortyapi.com/api/episode/9", 174 | "https://rickandmortyapi.com/api/episode/10", 175 | "https://rickandmortyapi.com/api/episode/11", 176 | "https://rickandmortyapi.com/api/episode/12", 177 | "https://rickandmortyapi.com/api/episode/14", 178 | "https://rickandmortyapi.com/api/episode/15", 179 | "https://rickandmortyapi.com/api/episode/16", 180 | "https://rickandmortyapi.com/api/episode/17", 181 | "https://rickandmortyapi.com/api/episode/18", 182 | "https://rickandmortyapi.com/api/episode/19", 183 | "https://rickandmortyapi.com/api/episode/20", 184 | "https://rickandmortyapi.com/api/episode/21", 185 | "https://rickandmortyapi.com/api/episode/22", 186 | "https://rickandmortyapi.com/api/episode/23", 187 | "https://rickandmortyapi.com/api/episode/24", 188 | "https://rickandmortyapi.com/api/episode/25", 189 | "https://rickandmortyapi.com/api/episode/26", 190 | "https://rickandmortyapi.com/api/episode/27", 191 | "https://rickandmortyapi.com/api/episode/29", 192 | "https://rickandmortyapi.com/api/episode/30", 193 | "https://rickandmortyapi.com/api/episode/31", 194 | "https://rickandmortyapi.com/api/episode/32", 195 | "https://rickandmortyapi.com/api/episode/33", 196 | "https://rickandmortyapi.com/api/episode/34", 197 | "https://rickandmortyapi.com/api/episode/35", 198 | "https://rickandmortyapi.com/api/episode/36", 199 | "https://rickandmortyapi.com/api/episode/38", 200 | "https://rickandmortyapi.com/api/episode/39", 201 | "https://rickandmortyapi.com/api/episode/40", 202 | "https://rickandmortyapi.com/api/episode/41", 203 | "https://rickandmortyapi.com/api/episode/42", 204 | "https://rickandmortyapi.com/api/episode/43", 205 | "https://rickandmortyapi.com/api/episode/44", 206 | "https://rickandmortyapi.com/api/episode/45", 207 | "https://rickandmortyapi.com/api/episode/46", 208 | "https://rickandmortyapi.com/api/episode/47", 209 | "https://rickandmortyapi.com/api/episode/48", 210 | "https://rickandmortyapi.com/api/episode/49", 211 | "https://rickandmortyapi.com/api/episode/51" 212 | ], 213 | "url": "https://rickandmortyapi.com/api/character/3", 214 | "created": "2017-11-04T19:09:56.428Z" 215 | }, 216 | { 217 | "id": 4, 218 | "name": "Beth Smith", 219 | "status": "Alive", 220 | "species": "Human", 221 | "type": "", 222 | "gender": "Female", 223 | "origin": { 224 | "name": "Earth (Replacement Dimension)", 225 | "url": "https://rickandmortyapi.com/api/location/20" 226 | }, 227 | "location": { 228 | "name": "Earth (Replacement Dimension)", 229 | "url": "https://rickandmortyapi.com/api/location/20" 230 | }, 231 | "image": "https://rickandmortyapi.com/api/character/avatar/4.jpeg", 232 | "episode": [ 233 | "https://rickandmortyapi.com/api/episode/6", 234 | "https://rickandmortyapi.com/api/episode/7", 235 | "https://rickandmortyapi.com/api/episode/8", 236 | "https://rickandmortyapi.com/api/episode/9", 237 | "https://rickandmortyapi.com/api/episode/10", 238 | "https://rickandmortyapi.com/api/episode/11", 239 | "https://rickandmortyapi.com/api/episode/12", 240 | "https://rickandmortyapi.com/api/episode/14", 241 | "https://rickandmortyapi.com/api/episode/15", 242 | "https://rickandmortyapi.com/api/episode/16", 243 | "https://rickandmortyapi.com/api/episode/18", 244 | "https://rickandmortyapi.com/api/episode/19", 245 | "https://rickandmortyapi.com/api/episode/20", 246 | "https://rickandmortyapi.com/api/episode/21", 247 | "https://rickandmortyapi.com/api/episode/22", 248 | "https://rickandmortyapi.com/api/episode/23", 249 | "https://rickandmortyapi.com/api/episode/24", 250 | "https://rickandmortyapi.com/api/episode/25", 251 | "https://rickandmortyapi.com/api/episode/26", 252 | "https://rickandmortyapi.com/api/episode/27", 253 | "https://rickandmortyapi.com/api/episode/28", 254 | "https://rickandmortyapi.com/api/episode/29", 255 | "https://rickandmortyapi.com/api/episode/30", 256 | "https://rickandmortyapi.com/api/episode/31", 257 | "https://rickandmortyapi.com/api/episode/32", 258 | "https://rickandmortyapi.com/api/episode/33", 259 | "https://rickandmortyapi.com/api/episode/34", 260 | "https://rickandmortyapi.com/api/episode/35", 261 | "https://rickandmortyapi.com/api/episode/36", 262 | "https://rickandmortyapi.com/api/episode/38", 263 | "https://rickandmortyapi.com/api/episode/39", 264 | "https://rickandmortyapi.com/api/episode/40", 265 | "https://rickandmortyapi.com/api/episode/41", 266 | "https://rickandmortyapi.com/api/episode/42", 267 | "https://rickandmortyapi.com/api/episode/43", 268 | "https://rickandmortyapi.com/api/episode/44", 269 | "https://rickandmortyapi.com/api/episode/45", 270 | "https://rickandmortyapi.com/api/episode/46", 271 | "https://rickandmortyapi.com/api/episode/47", 272 | "https://rickandmortyapi.com/api/episode/48", 273 | "https://rickandmortyapi.com/api/episode/49", 274 | "https://rickandmortyapi.com/api/episode/51" 275 | ], 276 | "url": "https://rickandmortyapi.com/api/character/4", 277 | "created": "2017-11-04T19:22:43.665Z" 278 | }, 279 | { 280 | "id": 5, 281 | "name": "Jerry Smith", 282 | "status": "Alive", 283 | "species": "Human", 284 | "type": "", 285 | "gender": "Male", 286 | "origin": { 287 | "name": "Earth (Replacement Dimension)", 288 | "url": "https://rickandmortyapi.com/api/location/20" 289 | }, 290 | "location": { 291 | "name": "Earth (Replacement Dimension)", 292 | "url": "https://rickandmortyapi.com/api/location/20" 293 | }, 294 | "image": "https://rickandmortyapi.com/api/character/avatar/5.jpeg", 295 | "episode": [ 296 | "https://rickandmortyapi.com/api/episode/6", 297 | "https://rickandmortyapi.com/api/episode/7", 298 | "https://rickandmortyapi.com/api/episode/8", 299 | "https://rickandmortyapi.com/api/episode/9", 300 | "https://rickandmortyapi.com/api/episode/10", 301 | "https://rickandmortyapi.com/api/episode/11", 302 | "https://rickandmortyapi.com/api/episode/12", 303 | "https://rickandmortyapi.com/api/episode/13", 304 | "https://rickandmortyapi.com/api/episode/14", 305 | "https://rickandmortyapi.com/api/episode/15", 306 | "https://rickandmortyapi.com/api/episode/16", 307 | "https://rickandmortyapi.com/api/episode/18", 308 | "https://rickandmortyapi.com/api/episode/19", 309 | "https://rickandmortyapi.com/api/episode/20", 310 | "https://rickandmortyapi.com/api/episode/21", 311 | "https://rickandmortyapi.com/api/episode/22", 312 | "https://rickandmortyapi.com/api/episode/23", 313 | "https://rickandmortyapi.com/api/episode/26", 314 | "https://rickandmortyapi.com/api/episode/29", 315 | "https://rickandmortyapi.com/api/episode/30", 316 | "https://rickandmortyapi.com/api/episode/31", 317 | "https://rickandmortyapi.com/api/episode/32", 318 | "https://rickandmortyapi.com/api/episode/33", 319 | "https://rickandmortyapi.com/api/episode/35", 320 | "https://rickandmortyapi.com/api/episode/36", 321 | "https://rickandmortyapi.com/api/episode/38", 322 | "https://rickandmortyapi.com/api/episode/39", 323 | "https://rickandmortyapi.com/api/episode/40", 324 | "https://rickandmortyapi.com/api/episode/41", 325 | "https://rickandmortyapi.com/api/episode/42", 326 | "https://rickandmortyapi.com/api/episode/43", 327 | "https://rickandmortyapi.com/api/episode/44", 328 | "https://rickandmortyapi.com/api/episode/45", 329 | "https://rickandmortyapi.com/api/episode/46", 330 | "https://rickandmortyapi.com/api/episode/47", 331 | "https://rickandmortyapi.com/api/episode/48", 332 | "https://rickandmortyapi.com/api/episode/49", 333 | "https://rickandmortyapi.com/api/episode/50", 334 | "https://rickandmortyapi.com/api/episode/51" 335 | ], 336 | "url": "https://rickandmortyapi.com/api/character/5", 337 | "created": "2017-11-04T19:26:56.301Z" 338 | }, 339 | { 340 | "id": 6, 341 | "name": "Abadango Cluster Princess", 342 | "status": "Alive", 343 | "species": "Alien", 344 | "type": "", 345 | "gender": "Female", 346 | "origin": { 347 | "name": "Abadango", 348 | "url": "https://rickandmortyapi.com/api/location/2" 349 | }, 350 | "location": { 351 | "name": "Abadango", 352 | "url": "https://rickandmortyapi.com/api/location/2" 353 | }, 354 | "image": "https://rickandmortyapi.com/api/character/avatar/6.jpeg", 355 | "episode": [ 356 | "https://rickandmortyapi.com/api/episode/27" 357 | ], 358 | "url": "https://rickandmortyapi.com/api/character/6", 359 | "created": "2017-11-04T19:50:28.250Z" 360 | }, 361 | { 362 | "id": 7, 363 | "name": "Abradolf Lincler", 364 | "status": "unknown", 365 | "species": "Human", 366 | "type": "Genetic experiment", 367 | "gender": "Male", 368 | "origin": { 369 | "name": "Earth (Replacement Dimension)", 370 | "url": "https://rickandmortyapi.com/api/location/20" 371 | }, 372 | "location": { 373 | "name": "Testicle Monster Dimension", 374 | "url": "https://rickandmortyapi.com/api/location/21" 375 | }, 376 | "image": "https://rickandmortyapi.com/api/character/avatar/7.jpeg", 377 | "episode": [ 378 | "https://rickandmortyapi.com/api/episode/10", 379 | "https://rickandmortyapi.com/api/episode/11" 380 | ], 381 | "url": "https://rickandmortyapi.com/api/character/7", 382 | "created": "2017-11-04T19:59:20.523Z" 383 | }, 384 | { 385 | "id": 8, 386 | "name": "Adjudicator Rick", 387 | "status": "Dead", 388 | "species": "Human", 389 | "type": "", 390 | "gender": "Male", 391 | "origin": { 392 | "name": "unknown", 393 | "url": "" 394 | }, 395 | "location": { 396 | "name": "Citadel of Ricks", 397 | "url": "https://rickandmortyapi.com/api/location/3" 398 | }, 399 | "image": "https://rickandmortyapi.com/api/character/avatar/8.jpeg", 400 | "episode": [ 401 | "https://rickandmortyapi.com/api/episode/28" 402 | ], 403 | "url": "https://rickandmortyapi.com/api/character/8", 404 | "created": "2017-11-04T20:03:34.737Z" 405 | }, 406 | { 407 | "id": 9, 408 | "name": "Agency Director", 409 | "status": "Dead", 410 | "species": "Human", 411 | "type": "", 412 | "gender": "Male", 413 | "origin": { 414 | "name": "Earth (Replacement Dimension)", 415 | "url": "https://rickandmortyapi.com/api/location/20" 416 | }, 417 | "location": { 418 | "name": "Earth (Replacement Dimension)", 419 | "url": "https://rickandmortyapi.com/api/location/20" 420 | }, 421 | "image": "https://rickandmortyapi.com/api/character/avatar/9.jpeg", 422 | "episode": [ 423 | "https://rickandmortyapi.com/api/episode/24" 424 | ], 425 | "url": "https://rickandmortyapi.com/api/character/9", 426 | "created": "2017-11-04T20:06:54.976Z" 427 | }, 428 | { 429 | "id": 10, 430 | "name": "Alan Rails", 431 | "status": "Dead", 432 | "species": "Human", 433 | "type": "Superhuman (Ghost trains summoner)", 434 | "gender": "Male", 435 | "origin": { 436 | "name": "unknown", 437 | "url": "" 438 | }, 439 | "location": { 440 | "name": "Worldender's lair", 441 | "url": "https://rickandmortyapi.com/api/location/4" 442 | }, 443 | "image": "https://rickandmortyapi.com/api/character/avatar/10.jpeg", 444 | "episode": [ 445 | "https://rickandmortyapi.com/api/episode/25" 446 | ], 447 | "url": "https://rickandmortyapi.com/api/character/10", 448 | "created": "2017-11-04T20:19:09.017Z" 449 | }, 450 | { 451 | "id": 11, 452 | "name": "Albert Einstein", 453 | "status": "Dead", 454 | "species": "Human", 455 | "type": "", 456 | "gender": "Male", 457 | "origin": { 458 | "name": "Earth (C-137)", 459 | "url": "https://rickandmortyapi.com/api/location/1" 460 | }, 461 | "location": { 462 | "name": "Earth (Replacement Dimension)", 463 | "url": "https://rickandmortyapi.com/api/location/20" 464 | }, 465 | "image": "https://rickandmortyapi.com/api/character/avatar/11.jpeg", 466 | "episode": [ 467 | "https://rickandmortyapi.com/api/episode/12" 468 | ], 469 | "url": "https://rickandmortyapi.com/api/character/11", 470 | "created": "2017-11-04T20:20:20.965Z" 471 | }, 472 | { 473 | "id": 12, 474 | "name": "Alexander", 475 | "status": "Dead", 476 | "species": "Human", 477 | "type": "", 478 | "gender": "Male", 479 | "origin": { 480 | "name": "Earth (C-137)", 481 | "url": "https://rickandmortyapi.com/api/location/1" 482 | }, 483 | "location": { 484 | "name": "Anatomy Park", 485 | "url": "https://rickandmortyapi.com/api/location/5" 486 | }, 487 | "image": "https://rickandmortyapi.com/api/character/avatar/12.jpeg", 488 | "episode": [ 489 | "https://rickandmortyapi.com/api/episode/3" 490 | ], 491 | "url": "https://rickandmortyapi.com/api/character/12", 492 | "created": "2017-11-04T20:32:33.144Z" 493 | }, 494 | { 495 | "id": 13, 496 | "name": "Alien Googah", 497 | "status": "unknown", 498 | "species": "Alien", 499 | "type": "", 500 | "gender": "unknown", 501 | "origin": { 502 | "name": "unknown", 503 | "url": "" 504 | }, 505 | "location": { 506 | "name": "Earth (Replacement Dimension)", 507 | "url": "https://rickandmortyapi.com/api/location/20" 508 | }, 509 | "image": "https://rickandmortyapi.com/api/character/avatar/13.jpeg", 510 | "episode": [ 511 | "https://rickandmortyapi.com/api/episode/31" 512 | ], 513 | "url": "https://rickandmortyapi.com/api/character/13", 514 | "created": "2017-11-04T20:33:30.779Z" 515 | }, 516 | { 517 | "id": 14, 518 | "name": "Alien Morty", 519 | "status": "unknown", 520 | "species": "Alien", 521 | "type": "", 522 | "gender": "Male", 523 | "origin": { 524 | "name": "unknown", 525 | "url": "" 526 | }, 527 | "location": { 528 | "name": "Citadel of Ricks", 529 | "url": "https://rickandmortyapi.com/api/location/3" 530 | }, 531 | "image": "https://rickandmortyapi.com/api/character/avatar/14.jpeg", 532 | "episode": [ 533 | "https://rickandmortyapi.com/api/episode/10" 534 | ], 535 | "url": "https://rickandmortyapi.com/api/character/14", 536 | "created": "2017-11-04T20:51:31.373Z" 537 | }, 538 | { 539 | "id": 15, 540 | "name": "Alien Rick", 541 | "status": "unknown", 542 | "species": "Alien", 543 | "type": "", 544 | "gender": "Male", 545 | "origin": { 546 | "name": "unknown", 547 | "url": "" 548 | }, 549 | "location": { 550 | "name": "Citadel of Ricks", 551 | "url": "https://rickandmortyapi.com/api/location/3" 552 | }, 553 | "image": "https://rickandmortyapi.com/api/character/avatar/15.jpeg", 554 | "episode": [ 555 | "https://rickandmortyapi.com/api/episode/10" 556 | ], 557 | "url": "https://rickandmortyapi.com/api/character/15", 558 | "created": "2017-11-04T20:56:13.215Z" 559 | }, 560 | { 561 | "id": 16, 562 | "name": "Amish Cyborg", 563 | "status": "Dead", 564 | "species": "Alien", 565 | "type": "Parasite", 566 | "gender": "Male", 567 | "origin": { 568 | "name": "unknown", 569 | "url": "" 570 | }, 571 | "location": { 572 | "name": "Earth (Replacement Dimension)", 573 | "url": "https://rickandmortyapi.com/api/location/20" 574 | }, 575 | "image": "https://rickandmortyapi.com/api/character/avatar/16.jpeg", 576 | "episode": [ 577 | "https://rickandmortyapi.com/api/episode/15" 578 | ], 579 | "url": "https://rickandmortyapi.com/api/character/16", 580 | "created": "2017-11-04T21:12:45.235Z" 581 | }, 582 | { 583 | "id": 17, 584 | "name": "Annie", 585 | "status": "Alive", 586 | "species": "Human", 587 | "type": "", 588 | "gender": "Female", 589 | "origin": { 590 | "name": "Earth (C-137)", 591 | "url": "https://rickandmortyapi.com/api/location/1" 592 | }, 593 | "location": { 594 | "name": "Anatomy Park", 595 | "url": "https://rickandmortyapi.com/api/location/5" 596 | }, 597 | "image": "https://rickandmortyapi.com/api/character/avatar/17.jpeg", 598 | "episode": [ 599 | "https://rickandmortyapi.com/api/episode/3" 600 | ], 601 | "url": "https://rickandmortyapi.com/api/character/17", 602 | "created": "2017-11-04T22:21:24.481Z" 603 | }, 604 | { 605 | "id": 18, 606 | "name": "Antenna Morty", 607 | "status": "Alive", 608 | "species": "Human", 609 | "type": "Human with antennae", 610 | "gender": "Male", 611 | "origin": { 612 | "name": "unknown", 613 | "url": "" 614 | }, 615 | "location": { 616 | "name": "Citadel of Ricks", 617 | "url": "https://rickandmortyapi.com/api/location/3" 618 | }, 619 | "image": "https://rickandmortyapi.com/api/character/avatar/18.jpeg", 620 | "episode": [ 621 | "https://rickandmortyapi.com/api/episode/10", 622 | "https://rickandmortyapi.com/api/episode/28" 623 | ], 624 | "url": "https://rickandmortyapi.com/api/character/18", 625 | "created": "2017-11-04T22:25:29.008Z" 626 | }, 627 | { 628 | "id": 19, 629 | "name": "Antenna Rick", 630 | "status": "unknown", 631 | "species": "Human", 632 | "type": "Human with antennae", 633 | "gender": "Male", 634 | "origin": { 635 | "name": "unknown", 636 | "url": "" 637 | }, 638 | "location": { 639 | "name": "unknown", 640 | "url": "" 641 | }, 642 | "image": "https://rickandmortyapi.com/api/character/avatar/19.jpeg", 643 | "episode": [ 644 | "https://rickandmortyapi.com/api/episode/10" 645 | ], 646 | "url": "https://rickandmortyapi.com/api/character/19", 647 | "created": "2017-11-04T22:28:13.756Z" 648 | }, 649 | { 650 | "id": 20, 651 | "name": "Ants in my Eyes Johnson", 652 | "status": "unknown", 653 | "species": "Human", 654 | "type": "Human with ants in his eyes", 655 | "gender": "Male", 656 | "origin": { 657 | "name": "unknown", 658 | "url": "" 659 | }, 660 | "location": { 661 | "name": "Interdimensional Cable", 662 | "url": "https://rickandmortyapi.com/api/location/6" 663 | }, 664 | "image": "https://rickandmortyapi.com/api/character/avatar/20.jpeg", 665 | "episode": [ 666 | "https://rickandmortyapi.com/api/episode/8" 667 | ], 668 | "url": "https://rickandmortyapi.com/api/character/20", 669 | "created": "2017-11-04T22:34:53.659Z" 670 | } 671 | ] 672 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/02-esmodules/src/service.js: -------------------------------------------------------------------------------- 1 | export default class Service { 2 | constructor({ url }) { 3 | this.url = url 4 | } 5 | async getCharacters({ skip, limit }) { 6 | const data = (await (await fetch(this.url)).json()).results 7 | return data.slice(skip, limit) 8 | } 9 | } -------------------------------------------------------------------------------- /01-ecmascript-modules/03-shared-code+import-from-url/node/index.mjs: -------------------------------------------------------------------------------- 1 | // node --experimental-network-imports index.mjs 2 | import CurrencyManager from "https://cdn.jsdelivr.net/gh/ErickWendel/lives-aquecimento03-javascript-expert@main/01-esmodules/esmodule/shared/currencyManager.mjs" 3 | // import CurrencyManager from "../shared/currencyManager.mjs" 4 | 5 | async function updateOutput(value) { 6 | console.log(value) 7 | } 8 | 9 | const inputMonitor = (value) => updateOutput(CurrencyManager.format(value)) 10 | 11 | const input = process.openStdin() 12 | console.log('typing...') 13 | input.addListener("data", inputMonitor) -------------------------------------------------------------------------------- /01-ecmascript-modules/03-shared-code+import-from-url/shared/currencyManager.mjs: -------------------------------------------------------------------------------- 1 | // nesse caso, essa variavel nao fica global 2 | const currencyFormat = new Intl.NumberFormat('pt-br', { 3 | style: 'currency', 4 | currency: 'BRL' 5 | }) 6 | 7 | export default class CurrencyManager { 8 | static format(valor) { 9 | return currencyFormat.format(valor) 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /01-ecmascript-modules/03-shared-code+import-from-url/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Common JS Example 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /01-ecmascript-modules/03-shared-code+import-from-url/web/index.mjs: -------------------------------------------------------------------------------- 1 | // https://www.jsdelivr.com/github 2 | // import CurrencyManager from "../shared/currencyManager.mjs" 3 | import CurrencyManager from "https://cdn.jsdelivr.net/gh/ErickWendel/lives-aquecimento03-javascript-expert@main/01-esmodules/esmodule/shared/currencyManager.mjs" 4 | 5 | function updateOutput(value) { 6 | const output = document.getElementById("output") 7 | output.innerText = value 8 | } 9 | 10 | window.inputMonitor = (value) => updateOutput(CurrencyManager.format(value)) -------------------------------------------------------------------------------- /02-design-patterns/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
        11 | 12 | 13 | -------------------------------------------------------------------------------- /02-design-patterns/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-esmodules", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "01-esmodules", 9 | "version": "0.0.1", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "http-server": "^14.1.1" 13 | } 14 | }, 15 | "node_modules/ansi-styles": { 16 | "version": "4.3.0", 17 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 18 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 19 | "dev": true, 20 | "dependencies": { 21 | "color-convert": "^2.0.1" 22 | }, 23 | "engines": { 24 | "node": ">=8" 25 | }, 26 | "funding": { 27 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 28 | } 29 | }, 30 | "node_modules/async": { 31 | "version": "2.6.4", 32 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", 33 | "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", 34 | "dev": true, 35 | "dependencies": { 36 | "lodash": "^4.17.14" 37 | } 38 | }, 39 | "node_modules/basic-auth": { 40 | "version": "2.0.1", 41 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 42 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 43 | "dev": true, 44 | "dependencies": { 45 | "safe-buffer": "5.1.2" 46 | }, 47 | "engines": { 48 | "node": ">= 0.8" 49 | } 50 | }, 51 | "node_modules/call-bind": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 54 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 55 | "dev": true, 56 | "dependencies": { 57 | "function-bind": "^1.1.1", 58 | "get-intrinsic": "^1.0.2" 59 | }, 60 | "funding": { 61 | "url": "https://github.com/sponsors/ljharb" 62 | } 63 | }, 64 | "node_modules/chalk": { 65 | "version": "4.1.2", 66 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 67 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 68 | "dev": true, 69 | "dependencies": { 70 | "ansi-styles": "^4.1.0", 71 | "supports-color": "^7.1.0" 72 | }, 73 | "engines": { 74 | "node": ">=10" 75 | }, 76 | "funding": { 77 | "url": "https://github.com/chalk/chalk?sponsor=1" 78 | } 79 | }, 80 | "node_modules/color-convert": { 81 | "version": "2.0.1", 82 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 83 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 84 | "dev": true, 85 | "dependencies": { 86 | "color-name": "~1.1.4" 87 | }, 88 | "engines": { 89 | "node": ">=7.0.0" 90 | } 91 | }, 92 | "node_modules/color-name": { 93 | "version": "1.1.4", 94 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 95 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 96 | "dev": true 97 | }, 98 | "node_modules/corser": { 99 | "version": "2.0.1", 100 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 101 | "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", 102 | "dev": true, 103 | "engines": { 104 | "node": ">= 0.4.0" 105 | } 106 | }, 107 | "node_modules/debug": { 108 | "version": "3.2.7", 109 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 110 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 111 | "dev": true, 112 | "dependencies": { 113 | "ms": "^2.1.1" 114 | } 115 | }, 116 | "node_modules/eventemitter3": { 117 | "version": "4.0.7", 118 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 119 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 120 | "dev": true 121 | }, 122 | "node_modules/follow-redirects": { 123 | "version": "1.15.2", 124 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 125 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 126 | "dev": true, 127 | "funding": [ 128 | { 129 | "type": "individual", 130 | "url": "https://github.com/sponsors/RubenVerborgh" 131 | } 132 | ], 133 | "engines": { 134 | "node": ">=4.0" 135 | }, 136 | "peerDependenciesMeta": { 137 | "debug": { 138 | "optional": true 139 | } 140 | } 141 | }, 142 | "node_modules/function-bind": { 143 | "version": "1.1.1", 144 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 145 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 146 | "dev": true 147 | }, 148 | "node_modules/get-intrinsic": { 149 | "version": "1.1.3", 150 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", 151 | "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", 152 | "dev": true, 153 | "dependencies": { 154 | "function-bind": "^1.1.1", 155 | "has": "^1.0.3", 156 | "has-symbols": "^1.0.3" 157 | }, 158 | "funding": { 159 | "url": "https://github.com/sponsors/ljharb" 160 | } 161 | }, 162 | "node_modules/has": { 163 | "version": "1.0.3", 164 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 165 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 166 | "dev": true, 167 | "dependencies": { 168 | "function-bind": "^1.1.1" 169 | }, 170 | "engines": { 171 | "node": ">= 0.4.0" 172 | } 173 | }, 174 | "node_modules/has-flag": { 175 | "version": "4.0.0", 176 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 177 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 178 | "dev": true, 179 | "engines": { 180 | "node": ">=8" 181 | } 182 | }, 183 | "node_modules/has-symbols": { 184 | "version": "1.0.3", 185 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 186 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 187 | "dev": true, 188 | "engines": { 189 | "node": ">= 0.4" 190 | }, 191 | "funding": { 192 | "url": "https://github.com/sponsors/ljharb" 193 | } 194 | }, 195 | "node_modules/he": { 196 | "version": "1.2.0", 197 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 198 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 199 | "dev": true, 200 | "bin": { 201 | "he": "bin/he" 202 | } 203 | }, 204 | "node_modules/html-encoding-sniffer": { 205 | "version": "3.0.0", 206 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 207 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 208 | "dev": true, 209 | "dependencies": { 210 | "whatwg-encoding": "^2.0.0" 211 | }, 212 | "engines": { 213 | "node": ">=12" 214 | } 215 | }, 216 | "node_modules/http-proxy": { 217 | "version": "1.18.1", 218 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 219 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 220 | "dev": true, 221 | "dependencies": { 222 | "eventemitter3": "^4.0.0", 223 | "follow-redirects": "^1.0.0", 224 | "requires-port": "^1.0.0" 225 | }, 226 | "engines": { 227 | "node": ">=8.0.0" 228 | } 229 | }, 230 | "node_modules/http-server": { 231 | "version": "14.1.1", 232 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", 233 | "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", 234 | "dev": true, 235 | "dependencies": { 236 | "basic-auth": "^2.0.1", 237 | "chalk": "^4.1.2", 238 | "corser": "^2.0.1", 239 | "he": "^1.2.0", 240 | "html-encoding-sniffer": "^3.0.0", 241 | "http-proxy": "^1.18.1", 242 | "mime": "^1.6.0", 243 | "minimist": "^1.2.6", 244 | "opener": "^1.5.1", 245 | "portfinder": "^1.0.28", 246 | "secure-compare": "3.0.1", 247 | "union": "~0.5.0", 248 | "url-join": "^4.0.1" 249 | }, 250 | "bin": { 251 | "http-server": "bin/http-server" 252 | }, 253 | "engines": { 254 | "node": ">=12" 255 | } 256 | }, 257 | "node_modules/iconv-lite": { 258 | "version": "0.6.3", 259 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 260 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 261 | "dev": true, 262 | "dependencies": { 263 | "safer-buffer": ">= 2.1.2 < 3.0.0" 264 | }, 265 | "engines": { 266 | "node": ">=0.10.0" 267 | } 268 | }, 269 | "node_modules/lodash": { 270 | "version": "4.17.21", 271 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 272 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 273 | "dev": true 274 | }, 275 | "node_modules/mime": { 276 | "version": "1.6.0", 277 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 278 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 279 | "dev": true, 280 | "bin": { 281 | "mime": "cli.js" 282 | }, 283 | "engines": { 284 | "node": ">=4" 285 | } 286 | }, 287 | "node_modules/minimist": { 288 | "version": "1.2.7", 289 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 290 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 291 | "dev": true, 292 | "funding": { 293 | "url": "https://github.com/sponsors/ljharb" 294 | } 295 | }, 296 | "node_modules/mkdirp": { 297 | "version": "0.5.6", 298 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 299 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 300 | "dev": true, 301 | "dependencies": { 302 | "minimist": "^1.2.6" 303 | }, 304 | "bin": { 305 | "mkdirp": "bin/cmd.js" 306 | } 307 | }, 308 | "node_modules/ms": { 309 | "version": "2.1.3", 310 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 311 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 312 | "dev": true 313 | }, 314 | "node_modules/object-inspect": { 315 | "version": "1.12.2", 316 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 317 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", 318 | "dev": true, 319 | "funding": { 320 | "url": "https://github.com/sponsors/ljharb" 321 | } 322 | }, 323 | "node_modules/opener": { 324 | "version": "1.5.2", 325 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 326 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 327 | "dev": true, 328 | "bin": { 329 | "opener": "bin/opener-bin.js" 330 | } 331 | }, 332 | "node_modules/portfinder": { 333 | "version": "1.0.32", 334 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", 335 | "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", 336 | "dev": true, 337 | "dependencies": { 338 | "async": "^2.6.4", 339 | "debug": "^3.2.7", 340 | "mkdirp": "^0.5.6" 341 | }, 342 | "engines": { 343 | "node": ">= 0.12.0" 344 | } 345 | }, 346 | "node_modules/qs": { 347 | "version": "6.11.0", 348 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 349 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 350 | "dev": true, 351 | "dependencies": { 352 | "side-channel": "^1.0.4" 353 | }, 354 | "engines": { 355 | "node": ">=0.6" 356 | }, 357 | "funding": { 358 | "url": "https://github.com/sponsors/ljharb" 359 | } 360 | }, 361 | "node_modules/requires-port": { 362 | "version": "1.0.0", 363 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 364 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 365 | "dev": true 366 | }, 367 | "node_modules/safe-buffer": { 368 | "version": "5.1.2", 369 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 370 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 371 | "dev": true 372 | }, 373 | "node_modules/safer-buffer": { 374 | "version": "2.1.2", 375 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 376 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 377 | "dev": true 378 | }, 379 | "node_modules/secure-compare": { 380 | "version": "3.0.1", 381 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 382 | "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", 383 | "dev": true 384 | }, 385 | "node_modules/side-channel": { 386 | "version": "1.0.4", 387 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 388 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 389 | "dev": true, 390 | "dependencies": { 391 | "call-bind": "^1.0.0", 392 | "get-intrinsic": "^1.0.2", 393 | "object-inspect": "^1.9.0" 394 | }, 395 | "funding": { 396 | "url": "https://github.com/sponsors/ljharb" 397 | } 398 | }, 399 | "node_modules/supports-color": { 400 | "version": "7.2.0", 401 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 402 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 403 | "dev": true, 404 | "dependencies": { 405 | "has-flag": "^4.0.0" 406 | }, 407 | "engines": { 408 | "node": ">=8" 409 | } 410 | }, 411 | "node_modules/union": { 412 | "version": "0.5.0", 413 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 414 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 415 | "dev": true, 416 | "dependencies": { 417 | "qs": "^6.4.0" 418 | }, 419 | "engines": { 420 | "node": ">= 0.8.0" 421 | } 422 | }, 423 | "node_modules/url-join": { 424 | "version": "4.0.1", 425 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", 426 | "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", 427 | "dev": true 428 | }, 429 | "node_modules/whatwg-encoding": { 430 | "version": "2.0.0", 431 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 432 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 433 | "dev": true, 434 | "dependencies": { 435 | "iconv-lite": "0.6.3" 436 | }, 437 | "engines": { 438 | "node": ">=12" 439 | } 440 | } 441 | }, 442 | "dependencies": { 443 | "ansi-styles": { 444 | "version": "4.3.0", 445 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 446 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 447 | "dev": true, 448 | "requires": { 449 | "color-convert": "^2.0.1" 450 | } 451 | }, 452 | "async": { 453 | "version": "2.6.4", 454 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", 455 | "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", 456 | "dev": true, 457 | "requires": { 458 | "lodash": "^4.17.14" 459 | } 460 | }, 461 | "basic-auth": { 462 | "version": "2.0.1", 463 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 464 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 465 | "dev": true, 466 | "requires": { 467 | "safe-buffer": "5.1.2" 468 | } 469 | }, 470 | "call-bind": { 471 | "version": "1.0.2", 472 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 473 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 474 | "dev": true, 475 | "requires": { 476 | "function-bind": "^1.1.1", 477 | "get-intrinsic": "^1.0.2" 478 | } 479 | }, 480 | "chalk": { 481 | "version": "4.1.2", 482 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 483 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 484 | "dev": true, 485 | "requires": { 486 | "ansi-styles": "^4.1.0", 487 | "supports-color": "^7.1.0" 488 | } 489 | }, 490 | "color-convert": { 491 | "version": "2.0.1", 492 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 493 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 494 | "dev": true, 495 | "requires": { 496 | "color-name": "~1.1.4" 497 | } 498 | }, 499 | "color-name": { 500 | "version": "1.1.4", 501 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 502 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 503 | "dev": true 504 | }, 505 | "corser": { 506 | "version": "2.0.1", 507 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 508 | "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", 509 | "dev": true 510 | }, 511 | "debug": { 512 | "version": "3.2.7", 513 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 514 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 515 | "dev": true, 516 | "requires": { 517 | "ms": "^2.1.1" 518 | } 519 | }, 520 | "eventemitter3": { 521 | "version": "4.0.7", 522 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 523 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 524 | "dev": true 525 | }, 526 | "follow-redirects": { 527 | "version": "1.15.2", 528 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 529 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 530 | "dev": true 531 | }, 532 | "function-bind": { 533 | "version": "1.1.1", 534 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 535 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 536 | "dev": true 537 | }, 538 | "get-intrinsic": { 539 | "version": "1.1.3", 540 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", 541 | "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", 542 | "dev": true, 543 | "requires": { 544 | "function-bind": "^1.1.1", 545 | "has": "^1.0.3", 546 | "has-symbols": "^1.0.3" 547 | } 548 | }, 549 | "has": { 550 | "version": "1.0.3", 551 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 552 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 553 | "dev": true, 554 | "requires": { 555 | "function-bind": "^1.1.1" 556 | } 557 | }, 558 | "has-flag": { 559 | "version": "4.0.0", 560 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 561 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 562 | "dev": true 563 | }, 564 | "has-symbols": { 565 | "version": "1.0.3", 566 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 567 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 568 | "dev": true 569 | }, 570 | "he": { 571 | "version": "1.2.0", 572 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 573 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 574 | "dev": true 575 | }, 576 | "html-encoding-sniffer": { 577 | "version": "3.0.0", 578 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", 579 | "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", 580 | "dev": true, 581 | "requires": { 582 | "whatwg-encoding": "^2.0.0" 583 | } 584 | }, 585 | "http-proxy": { 586 | "version": "1.18.1", 587 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 588 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 589 | "dev": true, 590 | "requires": { 591 | "eventemitter3": "^4.0.0", 592 | "follow-redirects": "^1.0.0", 593 | "requires-port": "^1.0.0" 594 | } 595 | }, 596 | "http-server": { 597 | "version": "14.1.1", 598 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", 599 | "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", 600 | "dev": true, 601 | "requires": { 602 | "basic-auth": "^2.0.1", 603 | "chalk": "^4.1.2", 604 | "corser": "^2.0.1", 605 | "he": "^1.2.0", 606 | "html-encoding-sniffer": "^3.0.0", 607 | "http-proxy": "^1.18.1", 608 | "mime": "^1.6.0", 609 | "minimist": "^1.2.6", 610 | "opener": "^1.5.1", 611 | "portfinder": "^1.0.28", 612 | "secure-compare": "3.0.1", 613 | "union": "~0.5.0", 614 | "url-join": "^4.0.1" 615 | } 616 | }, 617 | "iconv-lite": { 618 | "version": "0.6.3", 619 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 620 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 621 | "dev": true, 622 | "requires": { 623 | "safer-buffer": ">= 2.1.2 < 3.0.0" 624 | } 625 | }, 626 | "lodash": { 627 | "version": "4.17.21", 628 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 629 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 630 | "dev": true 631 | }, 632 | "mime": { 633 | "version": "1.6.0", 634 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 635 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 636 | "dev": true 637 | }, 638 | "minimist": { 639 | "version": "1.2.7", 640 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 641 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 642 | "dev": true 643 | }, 644 | "mkdirp": { 645 | "version": "0.5.6", 646 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 647 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 648 | "dev": true, 649 | "requires": { 650 | "minimist": "^1.2.6" 651 | } 652 | }, 653 | "ms": { 654 | "version": "2.1.3", 655 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 656 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 657 | "dev": true 658 | }, 659 | "object-inspect": { 660 | "version": "1.12.2", 661 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", 662 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", 663 | "dev": true 664 | }, 665 | "opener": { 666 | "version": "1.5.2", 667 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 668 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 669 | "dev": true 670 | }, 671 | "portfinder": { 672 | "version": "1.0.32", 673 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", 674 | "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", 675 | "dev": true, 676 | "requires": { 677 | "async": "^2.6.4", 678 | "debug": "^3.2.7", 679 | "mkdirp": "^0.5.6" 680 | } 681 | }, 682 | "qs": { 683 | "version": "6.11.0", 684 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 685 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 686 | "dev": true, 687 | "requires": { 688 | "side-channel": "^1.0.4" 689 | } 690 | }, 691 | "requires-port": { 692 | "version": "1.0.0", 693 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 694 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 695 | "dev": true 696 | }, 697 | "safe-buffer": { 698 | "version": "5.1.2", 699 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 700 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 701 | "dev": true 702 | }, 703 | "safer-buffer": { 704 | "version": "2.1.2", 705 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 706 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 707 | "dev": true 708 | }, 709 | "secure-compare": { 710 | "version": "3.0.1", 711 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 712 | "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", 713 | "dev": true 714 | }, 715 | "side-channel": { 716 | "version": "1.0.4", 717 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 718 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 719 | "dev": true, 720 | "requires": { 721 | "call-bind": "^1.0.0", 722 | "get-intrinsic": "^1.0.2", 723 | "object-inspect": "^1.9.0" 724 | } 725 | }, 726 | "supports-color": { 727 | "version": "7.2.0", 728 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 729 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 730 | "dev": true, 731 | "requires": { 732 | "has-flag": "^4.0.0" 733 | } 734 | }, 735 | "union": { 736 | "version": "0.5.0", 737 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 738 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 739 | "dev": true, 740 | "requires": { 741 | "qs": "^6.4.0" 742 | } 743 | }, 744 | "url-join": { 745 | "version": "4.0.1", 746 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", 747 | "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", 748 | "dev": true 749 | }, 750 | "whatwg-encoding": { 751 | "version": "2.0.0", 752 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", 753 | "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", 754 | "dev": true, 755 | "requires": { 756 | "iconv-lite": "0.6.3" 757 | } 758 | } 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /02-design-patterns/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-esmodules", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx http-server .", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "http-server": "^14.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /02-design-patterns/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "/src/mock-api.json" 3 | 4 | } -------------------------------------------------------------------------------- /02-design-patterns/src/controller.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Controller { 3 | constructor({ service, view}) { 4 | this.service = service 5 | this.view = view 6 | } 7 | static async initialize(deps) { 8 | const controller = new Controller(deps) 9 | await controller.init() 10 | } 11 | 12 | async init() { 13 | const chars = await this.service.getCharacters({ skip: 0, limit: 5 }) 14 | const data = this.prepareItems(chars) 15 | this.view.updateTable(data) 16 | } 17 | 18 | prepareItems(chars) { 19 | return chars.map(({name, image}) => { 20 | return { 21 | isBold: /smith/i.test(name), 22 | name, 23 | image, 24 | } 25 | }) 26 | } 27 | } -------------------------------------------------------------------------------- /02-design-patterns/src/index.js: -------------------------------------------------------------------------------- 1 | import config from './config.json' assert { type: "json"} 2 | import Service from './service.js' 3 | import View from './view.js' 4 | 5 | import Controller from "./controller.js" 6 | 7 | await Controller.initialize({ 8 | service: new Service({ 9 | url: config.url 10 | }), 11 | view: new View() 12 | }) -------------------------------------------------------------------------------- /02-design-patterns/src/mock-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "count": 826, 4 | "pages": 42, 5 | "next": "https://rickandmortyapi.com/api/character?page=2", 6 | "prev": null 7 | }, 8 | "results": [ 9 | { 10 | "id": 1, 11 | "name": "Rick Sanchez", 12 | "status": "Alive", 13 | "species": "Human", 14 | "type": "", 15 | "gender": "Male", 16 | "origin": { 17 | "name": "Earth (C-137)", 18 | "url": "https://rickandmortyapi.com/api/location/1" 19 | }, 20 | "location": { 21 | "name": "Citadel of Ricks", 22 | "url": "https://rickandmortyapi.com/api/location/3" 23 | }, 24 | "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg", 25 | "episode": [ 26 | "https://rickandmortyapi.com/api/episode/1", 27 | "https://rickandmortyapi.com/api/episode/2", 28 | "https://rickandmortyapi.com/api/episode/3", 29 | "https://rickandmortyapi.com/api/episode/4", 30 | "https://rickandmortyapi.com/api/episode/5", 31 | "https://rickandmortyapi.com/api/episode/6", 32 | "https://rickandmortyapi.com/api/episode/7", 33 | "https://rickandmortyapi.com/api/episode/8", 34 | "https://rickandmortyapi.com/api/episode/9", 35 | "https://rickandmortyapi.com/api/episode/10", 36 | "https://rickandmortyapi.com/api/episode/11", 37 | "https://rickandmortyapi.com/api/episode/12", 38 | "https://rickandmortyapi.com/api/episode/13", 39 | "https://rickandmortyapi.com/api/episode/14", 40 | "https://rickandmortyapi.com/api/episode/15", 41 | "https://rickandmortyapi.com/api/episode/16", 42 | "https://rickandmortyapi.com/api/episode/17", 43 | "https://rickandmortyapi.com/api/episode/18", 44 | "https://rickandmortyapi.com/api/episode/19", 45 | "https://rickandmortyapi.com/api/episode/20", 46 | "https://rickandmortyapi.com/api/episode/21", 47 | "https://rickandmortyapi.com/api/episode/22", 48 | "https://rickandmortyapi.com/api/episode/23", 49 | "https://rickandmortyapi.com/api/episode/24", 50 | "https://rickandmortyapi.com/api/episode/25", 51 | "https://rickandmortyapi.com/api/episode/26", 52 | "https://rickandmortyapi.com/api/episode/27", 53 | "https://rickandmortyapi.com/api/episode/28", 54 | "https://rickandmortyapi.com/api/episode/29", 55 | "https://rickandmortyapi.com/api/episode/30", 56 | "https://rickandmortyapi.com/api/episode/31", 57 | "https://rickandmortyapi.com/api/episode/32", 58 | "https://rickandmortyapi.com/api/episode/33", 59 | "https://rickandmortyapi.com/api/episode/34", 60 | "https://rickandmortyapi.com/api/episode/35", 61 | "https://rickandmortyapi.com/api/episode/36", 62 | "https://rickandmortyapi.com/api/episode/37", 63 | "https://rickandmortyapi.com/api/episode/38", 64 | "https://rickandmortyapi.com/api/episode/39", 65 | "https://rickandmortyapi.com/api/episode/40", 66 | "https://rickandmortyapi.com/api/episode/41", 67 | "https://rickandmortyapi.com/api/episode/42", 68 | "https://rickandmortyapi.com/api/episode/43", 69 | "https://rickandmortyapi.com/api/episode/44", 70 | "https://rickandmortyapi.com/api/episode/45", 71 | "https://rickandmortyapi.com/api/episode/46", 72 | "https://rickandmortyapi.com/api/episode/47", 73 | "https://rickandmortyapi.com/api/episode/48", 74 | "https://rickandmortyapi.com/api/episode/49", 75 | "https://rickandmortyapi.com/api/episode/50", 76 | "https://rickandmortyapi.com/api/episode/51" 77 | ], 78 | "url": "https://rickandmortyapi.com/api/character/1", 79 | "created": "2017-11-04T18:48:46.250Z" 80 | }, 81 | { 82 | "id": 2, 83 | "name": "Morty Smith", 84 | "status": "Alive", 85 | "species": "Human", 86 | "type": "", 87 | "gender": "Male", 88 | "origin": { 89 | "name": "unknown", 90 | "url": "" 91 | }, 92 | "location": { 93 | "name": "Citadel of Ricks", 94 | "url": "https://rickandmortyapi.com/api/location/3" 95 | }, 96 | "image": "https://rickandmortyapi.com/api/character/avatar/2.jpeg", 97 | "episode": [ 98 | "https://rickandmortyapi.com/api/episode/1", 99 | "https://rickandmortyapi.com/api/episode/2", 100 | "https://rickandmortyapi.com/api/episode/3", 101 | "https://rickandmortyapi.com/api/episode/4", 102 | "https://rickandmortyapi.com/api/episode/5", 103 | "https://rickandmortyapi.com/api/episode/6", 104 | "https://rickandmortyapi.com/api/episode/7", 105 | "https://rickandmortyapi.com/api/episode/8", 106 | "https://rickandmortyapi.com/api/episode/9", 107 | "https://rickandmortyapi.com/api/episode/10", 108 | "https://rickandmortyapi.com/api/episode/11", 109 | "https://rickandmortyapi.com/api/episode/12", 110 | "https://rickandmortyapi.com/api/episode/13", 111 | "https://rickandmortyapi.com/api/episode/14", 112 | "https://rickandmortyapi.com/api/episode/15", 113 | "https://rickandmortyapi.com/api/episode/16", 114 | "https://rickandmortyapi.com/api/episode/17", 115 | "https://rickandmortyapi.com/api/episode/18", 116 | "https://rickandmortyapi.com/api/episode/19", 117 | "https://rickandmortyapi.com/api/episode/20", 118 | "https://rickandmortyapi.com/api/episode/21", 119 | "https://rickandmortyapi.com/api/episode/22", 120 | "https://rickandmortyapi.com/api/episode/23", 121 | "https://rickandmortyapi.com/api/episode/24", 122 | "https://rickandmortyapi.com/api/episode/25", 123 | "https://rickandmortyapi.com/api/episode/26", 124 | "https://rickandmortyapi.com/api/episode/27", 125 | "https://rickandmortyapi.com/api/episode/28", 126 | "https://rickandmortyapi.com/api/episode/29", 127 | "https://rickandmortyapi.com/api/episode/30", 128 | "https://rickandmortyapi.com/api/episode/31", 129 | "https://rickandmortyapi.com/api/episode/32", 130 | "https://rickandmortyapi.com/api/episode/33", 131 | "https://rickandmortyapi.com/api/episode/34", 132 | "https://rickandmortyapi.com/api/episode/35", 133 | "https://rickandmortyapi.com/api/episode/36", 134 | "https://rickandmortyapi.com/api/episode/37", 135 | "https://rickandmortyapi.com/api/episode/38", 136 | "https://rickandmortyapi.com/api/episode/39", 137 | "https://rickandmortyapi.com/api/episode/40", 138 | "https://rickandmortyapi.com/api/episode/41", 139 | "https://rickandmortyapi.com/api/episode/42", 140 | "https://rickandmortyapi.com/api/episode/43", 141 | "https://rickandmortyapi.com/api/episode/44", 142 | "https://rickandmortyapi.com/api/episode/45", 143 | "https://rickandmortyapi.com/api/episode/46", 144 | "https://rickandmortyapi.com/api/episode/47", 145 | "https://rickandmortyapi.com/api/episode/48", 146 | "https://rickandmortyapi.com/api/episode/49", 147 | "https://rickandmortyapi.com/api/episode/50", 148 | "https://rickandmortyapi.com/api/episode/51" 149 | ], 150 | "url": "https://rickandmortyapi.com/api/character/2", 151 | "created": "2017-11-04T18:50:21.651Z" 152 | }, 153 | { 154 | "id": 3, 155 | "name": "Summer Smith", 156 | "status": "Alive", 157 | "species": "Human", 158 | "type": "", 159 | "gender": "Female", 160 | "origin": { 161 | "name": "Earth (Replacement Dimension)", 162 | "url": "https://rickandmortyapi.com/api/location/20" 163 | }, 164 | "location": { 165 | "name": "Earth (Replacement Dimension)", 166 | "url": "https://rickandmortyapi.com/api/location/20" 167 | }, 168 | "image": "https://rickandmortyapi.com/api/character/avatar/3.jpeg", 169 | "episode": [ 170 | "https://rickandmortyapi.com/api/episode/6", 171 | "https://rickandmortyapi.com/api/episode/7", 172 | "https://rickandmortyapi.com/api/episode/8", 173 | "https://rickandmortyapi.com/api/episode/9", 174 | "https://rickandmortyapi.com/api/episode/10", 175 | "https://rickandmortyapi.com/api/episode/11", 176 | "https://rickandmortyapi.com/api/episode/12", 177 | "https://rickandmortyapi.com/api/episode/14", 178 | "https://rickandmortyapi.com/api/episode/15", 179 | "https://rickandmortyapi.com/api/episode/16", 180 | "https://rickandmortyapi.com/api/episode/17", 181 | "https://rickandmortyapi.com/api/episode/18", 182 | "https://rickandmortyapi.com/api/episode/19", 183 | "https://rickandmortyapi.com/api/episode/20", 184 | "https://rickandmortyapi.com/api/episode/21", 185 | "https://rickandmortyapi.com/api/episode/22", 186 | "https://rickandmortyapi.com/api/episode/23", 187 | "https://rickandmortyapi.com/api/episode/24", 188 | "https://rickandmortyapi.com/api/episode/25", 189 | "https://rickandmortyapi.com/api/episode/26", 190 | "https://rickandmortyapi.com/api/episode/27", 191 | "https://rickandmortyapi.com/api/episode/29", 192 | "https://rickandmortyapi.com/api/episode/30", 193 | "https://rickandmortyapi.com/api/episode/31", 194 | "https://rickandmortyapi.com/api/episode/32", 195 | "https://rickandmortyapi.com/api/episode/33", 196 | "https://rickandmortyapi.com/api/episode/34", 197 | "https://rickandmortyapi.com/api/episode/35", 198 | "https://rickandmortyapi.com/api/episode/36", 199 | "https://rickandmortyapi.com/api/episode/38", 200 | "https://rickandmortyapi.com/api/episode/39", 201 | "https://rickandmortyapi.com/api/episode/40", 202 | "https://rickandmortyapi.com/api/episode/41", 203 | "https://rickandmortyapi.com/api/episode/42", 204 | "https://rickandmortyapi.com/api/episode/43", 205 | "https://rickandmortyapi.com/api/episode/44", 206 | "https://rickandmortyapi.com/api/episode/45", 207 | "https://rickandmortyapi.com/api/episode/46", 208 | "https://rickandmortyapi.com/api/episode/47", 209 | "https://rickandmortyapi.com/api/episode/48", 210 | "https://rickandmortyapi.com/api/episode/49", 211 | "https://rickandmortyapi.com/api/episode/51" 212 | ], 213 | "url": "https://rickandmortyapi.com/api/character/3", 214 | "created": "2017-11-04T19:09:56.428Z" 215 | }, 216 | { 217 | "id": 4, 218 | "name": "Beth Smith", 219 | "status": "Alive", 220 | "species": "Human", 221 | "type": "", 222 | "gender": "Female", 223 | "origin": { 224 | "name": "Earth (Replacement Dimension)", 225 | "url": "https://rickandmortyapi.com/api/location/20" 226 | }, 227 | "location": { 228 | "name": "Earth (Replacement Dimension)", 229 | "url": "https://rickandmortyapi.com/api/location/20" 230 | }, 231 | "image": "https://rickandmortyapi.com/api/character/avatar/4.jpeg", 232 | "episode": [ 233 | "https://rickandmortyapi.com/api/episode/6", 234 | "https://rickandmortyapi.com/api/episode/7", 235 | "https://rickandmortyapi.com/api/episode/8", 236 | "https://rickandmortyapi.com/api/episode/9", 237 | "https://rickandmortyapi.com/api/episode/10", 238 | "https://rickandmortyapi.com/api/episode/11", 239 | "https://rickandmortyapi.com/api/episode/12", 240 | "https://rickandmortyapi.com/api/episode/14", 241 | "https://rickandmortyapi.com/api/episode/15", 242 | "https://rickandmortyapi.com/api/episode/16", 243 | "https://rickandmortyapi.com/api/episode/18", 244 | "https://rickandmortyapi.com/api/episode/19", 245 | "https://rickandmortyapi.com/api/episode/20", 246 | "https://rickandmortyapi.com/api/episode/21", 247 | "https://rickandmortyapi.com/api/episode/22", 248 | "https://rickandmortyapi.com/api/episode/23", 249 | "https://rickandmortyapi.com/api/episode/24", 250 | "https://rickandmortyapi.com/api/episode/25", 251 | "https://rickandmortyapi.com/api/episode/26", 252 | "https://rickandmortyapi.com/api/episode/27", 253 | "https://rickandmortyapi.com/api/episode/28", 254 | "https://rickandmortyapi.com/api/episode/29", 255 | "https://rickandmortyapi.com/api/episode/30", 256 | "https://rickandmortyapi.com/api/episode/31", 257 | "https://rickandmortyapi.com/api/episode/32", 258 | "https://rickandmortyapi.com/api/episode/33", 259 | "https://rickandmortyapi.com/api/episode/34", 260 | "https://rickandmortyapi.com/api/episode/35", 261 | "https://rickandmortyapi.com/api/episode/36", 262 | "https://rickandmortyapi.com/api/episode/38", 263 | "https://rickandmortyapi.com/api/episode/39", 264 | "https://rickandmortyapi.com/api/episode/40", 265 | "https://rickandmortyapi.com/api/episode/41", 266 | "https://rickandmortyapi.com/api/episode/42", 267 | "https://rickandmortyapi.com/api/episode/43", 268 | "https://rickandmortyapi.com/api/episode/44", 269 | "https://rickandmortyapi.com/api/episode/45", 270 | "https://rickandmortyapi.com/api/episode/46", 271 | "https://rickandmortyapi.com/api/episode/47", 272 | "https://rickandmortyapi.com/api/episode/48", 273 | "https://rickandmortyapi.com/api/episode/49", 274 | "https://rickandmortyapi.com/api/episode/51" 275 | ], 276 | "url": "https://rickandmortyapi.com/api/character/4", 277 | "created": "2017-11-04T19:22:43.665Z" 278 | }, 279 | { 280 | "id": 5, 281 | "name": "Jerry Smith", 282 | "status": "Alive", 283 | "species": "Human", 284 | "type": "", 285 | "gender": "Male", 286 | "origin": { 287 | "name": "Earth (Replacement Dimension)", 288 | "url": "https://rickandmortyapi.com/api/location/20" 289 | }, 290 | "location": { 291 | "name": "Earth (Replacement Dimension)", 292 | "url": "https://rickandmortyapi.com/api/location/20" 293 | }, 294 | "image": "https://rickandmortyapi.com/api/character/avatar/5.jpeg", 295 | "episode": [ 296 | "https://rickandmortyapi.com/api/episode/6", 297 | "https://rickandmortyapi.com/api/episode/7", 298 | "https://rickandmortyapi.com/api/episode/8", 299 | "https://rickandmortyapi.com/api/episode/9", 300 | "https://rickandmortyapi.com/api/episode/10", 301 | "https://rickandmortyapi.com/api/episode/11", 302 | "https://rickandmortyapi.com/api/episode/12", 303 | "https://rickandmortyapi.com/api/episode/13", 304 | "https://rickandmortyapi.com/api/episode/14", 305 | "https://rickandmortyapi.com/api/episode/15", 306 | "https://rickandmortyapi.com/api/episode/16", 307 | "https://rickandmortyapi.com/api/episode/18", 308 | "https://rickandmortyapi.com/api/episode/19", 309 | "https://rickandmortyapi.com/api/episode/20", 310 | "https://rickandmortyapi.com/api/episode/21", 311 | "https://rickandmortyapi.com/api/episode/22", 312 | "https://rickandmortyapi.com/api/episode/23", 313 | "https://rickandmortyapi.com/api/episode/26", 314 | "https://rickandmortyapi.com/api/episode/29", 315 | "https://rickandmortyapi.com/api/episode/30", 316 | "https://rickandmortyapi.com/api/episode/31", 317 | "https://rickandmortyapi.com/api/episode/32", 318 | "https://rickandmortyapi.com/api/episode/33", 319 | "https://rickandmortyapi.com/api/episode/35", 320 | "https://rickandmortyapi.com/api/episode/36", 321 | "https://rickandmortyapi.com/api/episode/38", 322 | "https://rickandmortyapi.com/api/episode/39", 323 | "https://rickandmortyapi.com/api/episode/40", 324 | "https://rickandmortyapi.com/api/episode/41", 325 | "https://rickandmortyapi.com/api/episode/42", 326 | "https://rickandmortyapi.com/api/episode/43", 327 | "https://rickandmortyapi.com/api/episode/44", 328 | "https://rickandmortyapi.com/api/episode/45", 329 | "https://rickandmortyapi.com/api/episode/46", 330 | "https://rickandmortyapi.com/api/episode/47", 331 | "https://rickandmortyapi.com/api/episode/48", 332 | "https://rickandmortyapi.com/api/episode/49", 333 | "https://rickandmortyapi.com/api/episode/50", 334 | "https://rickandmortyapi.com/api/episode/51" 335 | ], 336 | "url": "https://rickandmortyapi.com/api/character/5", 337 | "created": "2017-11-04T19:26:56.301Z" 338 | }, 339 | { 340 | "id": 6, 341 | "name": "Abadango Cluster Princess", 342 | "status": "Alive", 343 | "species": "Alien", 344 | "type": "", 345 | "gender": "Female", 346 | "origin": { 347 | "name": "Abadango", 348 | "url": "https://rickandmortyapi.com/api/location/2" 349 | }, 350 | "location": { 351 | "name": "Abadango", 352 | "url": "https://rickandmortyapi.com/api/location/2" 353 | }, 354 | "image": "https://rickandmortyapi.com/api/character/avatar/6.jpeg", 355 | "episode": [ 356 | "https://rickandmortyapi.com/api/episode/27" 357 | ], 358 | "url": "https://rickandmortyapi.com/api/character/6", 359 | "created": "2017-11-04T19:50:28.250Z" 360 | }, 361 | { 362 | "id": 7, 363 | "name": "Abradolf Lincler", 364 | "status": "unknown", 365 | "species": "Human", 366 | "type": "Genetic experiment", 367 | "gender": "Male", 368 | "origin": { 369 | "name": "Earth (Replacement Dimension)", 370 | "url": "https://rickandmortyapi.com/api/location/20" 371 | }, 372 | "location": { 373 | "name": "Testicle Monster Dimension", 374 | "url": "https://rickandmortyapi.com/api/location/21" 375 | }, 376 | "image": "https://rickandmortyapi.com/api/character/avatar/7.jpeg", 377 | "episode": [ 378 | "https://rickandmortyapi.com/api/episode/10", 379 | "https://rickandmortyapi.com/api/episode/11" 380 | ], 381 | "url": "https://rickandmortyapi.com/api/character/7", 382 | "created": "2017-11-04T19:59:20.523Z" 383 | }, 384 | { 385 | "id": 8, 386 | "name": "Adjudicator Rick", 387 | "status": "Dead", 388 | "species": "Human", 389 | "type": "", 390 | "gender": "Male", 391 | "origin": { 392 | "name": "unknown", 393 | "url": "" 394 | }, 395 | "location": { 396 | "name": "Citadel of Ricks", 397 | "url": "https://rickandmortyapi.com/api/location/3" 398 | }, 399 | "image": "https://rickandmortyapi.com/api/character/avatar/8.jpeg", 400 | "episode": [ 401 | "https://rickandmortyapi.com/api/episode/28" 402 | ], 403 | "url": "https://rickandmortyapi.com/api/character/8", 404 | "created": "2017-11-04T20:03:34.737Z" 405 | }, 406 | { 407 | "id": 9, 408 | "name": "Agency Director", 409 | "status": "Dead", 410 | "species": "Human", 411 | "type": "", 412 | "gender": "Male", 413 | "origin": { 414 | "name": "Earth (Replacement Dimension)", 415 | "url": "https://rickandmortyapi.com/api/location/20" 416 | }, 417 | "location": { 418 | "name": "Earth (Replacement Dimension)", 419 | "url": "https://rickandmortyapi.com/api/location/20" 420 | }, 421 | "image": "https://rickandmortyapi.com/api/character/avatar/9.jpeg", 422 | "episode": [ 423 | "https://rickandmortyapi.com/api/episode/24" 424 | ], 425 | "url": "https://rickandmortyapi.com/api/character/9", 426 | "created": "2017-11-04T20:06:54.976Z" 427 | }, 428 | { 429 | "id": 10, 430 | "name": "Alan Rails", 431 | "status": "Dead", 432 | "species": "Human", 433 | "type": "Superhuman (Ghost trains summoner)", 434 | "gender": "Male", 435 | "origin": { 436 | "name": "unknown", 437 | "url": "" 438 | }, 439 | "location": { 440 | "name": "Worldender's lair", 441 | "url": "https://rickandmortyapi.com/api/location/4" 442 | }, 443 | "image": "https://rickandmortyapi.com/api/character/avatar/10.jpeg", 444 | "episode": [ 445 | "https://rickandmortyapi.com/api/episode/25" 446 | ], 447 | "url": "https://rickandmortyapi.com/api/character/10", 448 | "created": "2017-11-04T20:19:09.017Z" 449 | }, 450 | { 451 | "id": 11, 452 | "name": "Albert Einstein", 453 | "status": "Dead", 454 | "species": "Human", 455 | "type": "", 456 | "gender": "Male", 457 | "origin": { 458 | "name": "Earth (C-137)", 459 | "url": "https://rickandmortyapi.com/api/location/1" 460 | }, 461 | "location": { 462 | "name": "Earth (Replacement Dimension)", 463 | "url": "https://rickandmortyapi.com/api/location/20" 464 | }, 465 | "image": "https://rickandmortyapi.com/api/character/avatar/11.jpeg", 466 | "episode": [ 467 | "https://rickandmortyapi.com/api/episode/12" 468 | ], 469 | "url": "https://rickandmortyapi.com/api/character/11", 470 | "created": "2017-11-04T20:20:20.965Z" 471 | }, 472 | { 473 | "id": 12, 474 | "name": "Alexander", 475 | "status": "Dead", 476 | "species": "Human", 477 | "type": "", 478 | "gender": "Male", 479 | "origin": { 480 | "name": "Earth (C-137)", 481 | "url": "https://rickandmortyapi.com/api/location/1" 482 | }, 483 | "location": { 484 | "name": "Anatomy Park", 485 | "url": "https://rickandmortyapi.com/api/location/5" 486 | }, 487 | "image": "https://rickandmortyapi.com/api/character/avatar/12.jpeg", 488 | "episode": [ 489 | "https://rickandmortyapi.com/api/episode/3" 490 | ], 491 | "url": "https://rickandmortyapi.com/api/character/12", 492 | "created": "2017-11-04T20:32:33.144Z" 493 | }, 494 | { 495 | "id": 13, 496 | "name": "Alien Googah", 497 | "status": "unknown", 498 | "species": "Alien", 499 | "type": "", 500 | "gender": "unknown", 501 | "origin": { 502 | "name": "unknown", 503 | "url": "" 504 | }, 505 | "location": { 506 | "name": "Earth (Replacement Dimension)", 507 | "url": "https://rickandmortyapi.com/api/location/20" 508 | }, 509 | "image": "https://rickandmortyapi.com/api/character/avatar/13.jpeg", 510 | "episode": [ 511 | "https://rickandmortyapi.com/api/episode/31" 512 | ], 513 | "url": "https://rickandmortyapi.com/api/character/13", 514 | "created": "2017-11-04T20:33:30.779Z" 515 | }, 516 | { 517 | "id": 14, 518 | "name": "Alien Morty", 519 | "status": "unknown", 520 | "species": "Alien", 521 | "type": "", 522 | "gender": "Male", 523 | "origin": { 524 | "name": "unknown", 525 | "url": "" 526 | }, 527 | "location": { 528 | "name": "Citadel of Ricks", 529 | "url": "https://rickandmortyapi.com/api/location/3" 530 | }, 531 | "image": "https://rickandmortyapi.com/api/character/avatar/14.jpeg", 532 | "episode": [ 533 | "https://rickandmortyapi.com/api/episode/10" 534 | ], 535 | "url": "https://rickandmortyapi.com/api/character/14", 536 | "created": "2017-11-04T20:51:31.373Z" 537 | }, 538 | { 539 | "id": 15, 540 | "name": "Alien Rick", 541 | "status": "unknown", 542 | "species": "Alien", 543 | "type": "", 544 | "gender": "Male", 545 | "origin": { 546 | "name": "unknown", 547 | "url": "" 548 | }, 549 | "location": { 550 | "name": "Citadel of Ricks", 551 | "url": "https://rickandmortyapi.com/api/location/3" 552 | }, 553 | "image": "https://rickandmortyapi.com/api/character/avatar/15.jpeg", 554 | "episode": [ 555 | "https://rickandmortyapi.com/api/episode/10" 556 | ], 557 | "url": "https://rickandmortyapi.com/api/character/15", 558 | "created": "2017-11-04T20:56:13.215Z" 559 | }, 560 | { 561 | "id": 16, 562 | "name": "Amish Cyborg", 563 | "status": "Dead", 564 | "species": "Alien", 565 | "type": "Parasite", 566 | "gender": "Male", 567 | "origin": { 568 | "name": "unknown", 569 | "url": "" 570 | }, 571 | "location": { 572 | "name": "Earth (Replacement Dimension)", 573 | "url": "https://rickandmortyapi.com/api/location/20" 574 | }, 575 | "image": "https://rickandmortyapi.com/api/character/avatar/16.jpeg", 576 | "episode": [ 577 | "https://rickandmortyapi.com/api/episode/15" 578 | ], 579 | "url": "https://rickandmortyapi.com/api/character/16", 580 | "created": "2017-11-04T21:12:45.235Z" 581 | }, 582 | { 583 | "id": 17, 584 | "name": "Annie", 585 | "status": "Alive", 586 | "species": "Human", 587 | "type": "", 588 | "gender": "Female", 589 | "origin": { 590 | "name": "Earth (C-137)", 591 | "url": "https://rickandmortyapi.com/api/location/1" 592 | }, 593 | "location": { 594 | "name": "Anatomy Park", 595 | "url": "https://rickandmortyapi.com/api/location/5" 596 | }, 597 | "image": "https://rickandmortyapi.com/api/character/avatar/17.jpeg", 598 | "episode": [ 599 | "https://rickandmortyapi.com/api/episode/3" 600 | ], 601 | "url": "https://rickandmortyapi.com/api/character/17", 602 | "created": "2017-11-04T22:21:24.481Z" 603 | }, 604 | { 605 | "id": 18, 606 | "name": "Antenna Morty", 607 | "status": "Alive", 608 | "species": "Human", 609 | "type": "Human with antennae", 610 | "gender": "Male", 611 | "origin": { 612 | "name": "unknown", 613 | "url": "" 614 | }, 615 | "location": { 616 | "name": "Citadel of Ricks", 617 | "url": "https://rickandmortyapi.com/api/location/3" 618 | }, 619 | "image": "https://rickandmortyapi.com/api/character/avatar/18.jpeg", 620 | "episode": [ 621 | "https://rickandmortyapi.com/api/episode/10", 622 | "https://rickandmortyapi.com/api/episode/28" 623 | ], 624 | "url": "https://rickandmortyapi.com/api/character/18", 625 | "created": "2017-11-04T22:25:29.008Z" 626 | }, 627 | { 628 | "id": 19, 629 | "name": "Antenna Rick", 630 | "status": "unknown", 631 | "species": "Human", 632 | "type": "Human with antennae", 633 | "gender": "Male", 634 | "origin": { 635 | "name": "unknown", 636 | "url": "" 637 | }, 638 | "location": { 639 | "name": "unknown", 640 | "url": "" 641 | }, 642 | "image": "https://rickandmortyapi.com/api/character/avatar/19.jpeg", 643 | "episode": [ 644 | "https://rickandmortyapi.com/api/episode/10" 645 | ], 646 | "url": "https://rickandmortyapi.com/api/character/19", 647 | "created": "2017-11-04T22:28:13.756Z" 648 | }, 649 | { 650 | "id": 20, 651 | "name": "Ants in my Eyes Johnson", 652 | "status": "unknown", 653 | "species": "Human", 654 | "type": "Human with ants in his eyes", 655 | "gender": "Male", 656 | "origin": { 657 | "name": "unknown", 658 | "url": "" 659 | }, 660 | "location": { 661 | "name": "Interdimensional Cable", 662 | "url": "https://rickandmortyapi.com/api/location/6" 663 | }, 664 | "image": "https://rickandmortyapi.com/api/character/avatar/20.jpeg", 665 | "episode": [ 666 | "https://rickandmortyapi.com/api/episode/8" 667 | ], 668 | "url": "https://rickandmortyapi.com/api/character/20", 669 | "created": "2017-11-04T22:34:53.659Z" 670 | } 671 | ] 672 | } -------------------------------------------------------------------------------- /02-design-patterns/src/service.js: -------------------------------------------------------------------------------- 1 | export default class Service { 2 | constructor({ url }) { 3 | this.url = url 4 | } 5 | async getCharacters({ skip, limit }) { 6 | const data = (await (await fetch(this.url)).json()).results 7 | return data.slice(skip, limit) 8 | } 9 | } -------------------------------------------------------------------------------- /02-design-patterns/src/view.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | updateTable(items) { 3 | const output = document.querySelector('#output') 4 | output.innerHTML = this.formatItems(items) 5 | } 6 | 7 | formatItems(items) { 8 | const toBold = ({ 9 | name, 10 | isBold 11 | }) => isBold ? `${name}` : name 12 | 13 | return items 14 | .map(item => `
      1. ${toBold(item)}
      2. `) 15 | .join("
        ") 16 | } 17 | } -------------------------------------------------------------------------------- /03-native-tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
          11 | 12 | 13 | -------------------------------------------------------------------------------- /03-native-tests/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-esmodules", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "01-esmodules", 9 | "version": "0.0.1", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "tap-spec": "^5.0.0" 13 | } 14 | }, 15 | "node_modules/ansi-regex": { 16 | "version": "2.1.1", 17 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 18 | "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", 19 | "dev": true, 20 | "engines": { 21 | "node": ">=0.10.0" 22 | } 23 | }, 24 | "node_modules/buffer-shims": { 25 | "version": "1.0.0", 26 | "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", 27 | "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==", 28 | "dev": true 29 | }, 30 | "node_modules/core-util-is": { 31 | "version": "1.0.3", 32 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 33 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 34 | "dev": true 35 | }, 36 | "node_modules/duplexer": { 37 | "version": "0.1.2", 38 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 39 | "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 40 | "dev": true 41 | }, 42 | "node_modules/escape-string-regexp": { 43 | "version": "1.0.5", 44 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 45 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 46 | "dev": true, 47 | "engines": { 48 | "node": ">=0.8.0" 49 | } 50 | }, 51 | "node_modules/figures": { 52 | "version": "1.7.0", 53 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 54 | "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", 55 | "dev": true, 56 | "dependencies": { 57 | "escape-string-regexp": "^1.0.5", 58 | "object-assign": "^4.1.0" 59 | }, 60 | "engines": { 61 | "node": ">=0.10.0" 62 | } 63 | }, 64 | "node_modules/has-ansi": { 65 | "version": "2.0.0", 66 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 67 | "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", 68 | "dev": true, 69 | "dependencies": { 70 | "ansi-regex": "^2.0.0" 71 | }, 72 | "engines": { 73 | "node": ">=0.10.0" 74 | } 75 | }, 76 | "node_modules/inherits": { 77 | "version": "2.0.4", 78 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 79 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 80 | "dev": true 81 | }, 82 | "node_modules/is-finite": { 83 | "version": "1.1.0", 84 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", 85 | "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", 86 | "dev": true, 87 | "engines": { 88 | "node": ">=0.10.0" 89 | }, 90 | "funding": { 91 | "url": "https://github.com/sponsors/sindresorhus" 92 | } 93 | }, 94 | "node_modules/isarray": { 95 | "version": "1.0.0", 96 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 97 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 98 | "dev": true 99 | }, 100 | "node_modules/lodash": { 101 | "version": "4.17.21", 102 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 103 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 104 | "dev": true 105 | }, 106 | "node_modules/object-assign": { 107 | "version": "4.1.1", 108 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 109 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 110 | "dev": true, 111 | "engines": { 112 | "node": ">=0.10.0" 113 | } 114 | }, 115 | "node_modules/parse-ms": { 116 | "version": "1.0.1", 117 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", 118 | "integrity": "sha512-LpH1Cf5EYuVjkBvCDBYvkUPh+iv2bk3FHflxHkpCYT0/FZ1d3N3uJaLiHr4yGuMcFUhv6eAivitTvWZI4B/chg==", 119 | "dev": true, 120 | "engines": { 121 | "node": ">=0.10.0" 122 | } 123 | }, 124 | "node_modules/plur": { 125 | "version": "1.0.0", 126 | "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", 127 | "integrity": "sha512-qSnKBSZeDY8ApxwhfVIwKwF36KVJqb1/9nzYYq3j3vdwocULCXT8f8fQGkiw1Nk9BGfxiDagEe/pwakA+bOBqw==", 128 | "dev": true, 129 | "engines": { 130 | "node": ">=0.10.0" 131 | } 132 | }, 133 | "node_modules/pretty-ms": { 134 | "version": "2.1.0", 135 | "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", 136 | "integrity": "sha512-H2enpsxzDhuzRl3zeSQpQMirn8dB0Z/gxW96j06tMfTviUWvX14gjKb7qd1gtkUyYhDPuoNe00K5PqNvy2oQNg==", 137 | "dev": true, 138 | "dependencies": { 139 | "is-finite": "^1.0.1", 140 | "parse-ms": "^1.0.0", 141 | "plur": "^1.0.0" 142 | }, 143 | "engines": { 144 | "node": ">=0.10.0" 145 | } 146 | }, 147 | "node_modules/process-nextick-args": { 148 | "version": "1.0.7", 149 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 150 | "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==", 151 | "dev": true 152 | }, 153 | "node_modules/re-emitter": { 154 | "version": "1.1.3", 155 | "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.3.tgz", 156 | "integrity": "sha512-bHJul9CWcocrS+w5e5QrKYXV9NkbSA9hxSEyhYuctwm6keY9NXR2Xt/4A0vbMP0QvuwyfEyb4bkowYXv1ziEbg==", 157 | "dev": true 158 | }, 159 | "node_modules/readable-stream": { 160 | "version": "2.2.9", 161 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", 162 | "integrity": "sha512-iuxqX7b7FYt08AriYECxUsK9KTXE3A/FenxIa3IPmvANHxaTP/wGIwwf+IidvvIDk/MsCp/oEV6A8CXo4SDcCg==", 163 | "dev": true, 164 | "dependencies": { 165 | "buffer-shims": "~1.0.0", 166 | "core-util-is": "~1.0.0", 167 | "inherits": "~2.0.1", 168 | "isarray": "~1.0.0", 169 | "process-nextick-args": "~1.0.6", 170 | "string_decoder": "~1.0.0", 171 | "util-deprecate": "~1.0.1" 172 | } 173 | }, 174 | "node_modules/repeat-string": { 175 | "version": "1.6.1", 176 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 177 | "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", 178 | "dev": true, 179 | "engines": { 180 | "node": ">=0.10" 181 | } 182 | }, 183 | "node_modules/safe-buffer": { 184 | "version": "5.1.2", 185 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 186 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 187 | "dev": true 188 | }, 189 | "node_modules/split": { 190 | "version": "1.0.0", 191 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", 192 | "integrity": "sha512-3SVfJe2A0WZg3D+ZEtXqYkvpSGAVaZ1MgufNCeHioBESCqQFsuT1VcQufiopBfJZqh92ZwQ6ddL378iUSbqVNQ==", 193 | "dev": true, 194 | "dependencies": { 195 | "through": "2" 196 | }, 197 | "engines": { 198 | "node": "*" 199 | } 200 | }, 201 | "node_modules/string_decoder": { 202 | "version": "1.0.3", 203 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 204 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 205 | "dev": true, 206 | "dependencies": { 207 | "safe-buffer": "~5.1.0" 208 | } 209 | }, 210 | "node_modules/strip-ansi": { 211 | "version": "3.0.1", 212 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 213 | "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", 214 | "dev": true, 215 | "dependencies": { 216 | "ansi-regex": "^2.0.0" 217 | }, 218 | "engines": { 219 | "node": ">=0.10.0" 220 | } 221 | }, 222 | "node_modules/tap-out": { 223 | "version": "2.1.0", 224 | "resolved": "https://registry.npmjs.org/tap-out/-/tap-out-2.1.0.tgz", 225 | "integrity": "sha512-LJE+TBoVbOWhwdz4+FQk40nmbIuxJLqaGvj3WauQw3NYYU5TdjoV3C0x/yq37YAvVyi+oeBXmWnxWSjJ7IEyUw==", 226 | "dev": true, 227 | "dependencies": { 228 | "re-emitter": "1.1.3", 229 | "readable-stream": "2.2.9", 230 | "split": "1.0.0", 231 | "trim": "0.0.1" 232 | }, 233 | "bin": { 234 | "tap-out": "bin/cmd.js" 235 | } 236 | }, 237 | "node_modules/tap-spec": { 238 | "version": "5.0.0", 239 | "resolved": "https://registry.npmjs.org/tap-spec/-/tap-spec-5.0.0.tgz", 240 | "integrity": "sha512-zMDVJiE5I6Y4XGjlueGXJIX2YIkbDN44broZlnypT38Hj/czfOXrszHNNJBF/DXR8n+x6gbfSx68x04kIEHdrw==", 241 | "dev": true, 242 | "dependencies": { 243 | "chalk": "^1.0.0", 244 | "duplexer": "^0.1.1", 245 | "figures": "^1.4.0", 246 | "lodash": "^4.17.10", 247 | "pretty-ms": "^2.1.0", 248 | "repeat-string": "^1.5.2", 249 | "tap-out": "^2.1.0", 250 | "through2": "^2.0.0" 251 | }, 252 | "bin": { 253 | "tap-spec": "bin/cmd.js", 254 | "tspec": "bin/cmd.js" 255 | } 256 | }, 257 | "node_modules/tap-spec/node_modules/ansi-styles": { 258 | "version": "2.2.1", 259 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 260 | "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", 261 | "dev": true, 262 | "engines": { 263 | "node": ">=0.10.0" 264 | } 265 | }, 266 | "node_modules/tap-spec/node_modules/chalk": { 267 | "version": "1.1.3", 268 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 269 | "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", 270 | "dev": true, 271 | "dependencies": { 272 | "ansi-styles": "^2.2.1", 273 | "escape-string-regexp": "^1.0.2", 274 | "has-ansi": "^2.0.0", 275 | "strip-ansi": "^3.0.0", 276 | "supports-color": "^2.0.0" 277 | }, 278 | "engines": { 279 | "node": ">=0.10.0" 280 | } 281 | }, 282 | "node_modules/tap-spec/node_modules/supports-color": { 283 | "version": "2.0.0", 284 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 285 | "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", 286 | "dev": true, 287 | "engines": { 288 | "node": ">=0.8.0" 289 | } 290 | }, 291 | "node_modules/through": { 292 | "version": "2.3.8", 293 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 294 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", 295 | "dev": true 296 | }, 297 | "node_modules/through2": { 298 | "version": "2.0.5", 299 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 300 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 301 | "dev": true, 302 | "dependencies": { 303 | "readable-stream": "~2.3.6", 304 | "xtend": "~4.0.1" 305 | } 306 | }, 307 | "node_modules/through2/node_modules/process-nextick-args": { 308 | "version": "2.0.1", 309 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 310 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 311 | "dev": true 312 | }, 313 | "node_modules/through2/node_modules/readable-stream": { 314 | "version": "2.3.8", 315 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 316 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 317 | "dev": true, 318 | "dependencies": { 319 | "core-util-is": "~1.0.0", 320 | "inherits": "~2.0.3", 321 | "isarray": "~1.0.0", 322 | "process-nextick-args": "~2.0.0", 323 | "safe-buffer": "~5.1.1", 324 | "string_decoder": "~1.1.1", 325 | "util-deprecate": "~1.0.1" 326 | } 327 | }, 328 | "node_modules/through2/node_modules/string_decoder": { 329 | "version": "1.1.1", 330 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 331 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 332 | "dev": true, 333 | "dependencies": { 334 | "safe-buffer": "~5.1.0" 335 | } 336 | }, 337 | "node_modules/trim": { 338 | "version": "0.0.1", 339 | "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", 340 | "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", 341 | "deprecated": "Use String.prototype.trim() instead", 342 | "dev": true 343 | }, 344 | "node_modules/util-deprecate": { 345 | "version": "1.0.2", 346 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 347 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 348 | "dev": true 349 | }, 350 | "node_modules/xtend": { 351 | "version": "4.0.2", 352 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 353 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 354 | "dev": true, 355 | "engines": { 356 | "node": ">=0.4" 357 | } 358 | } 359 | }, 360 | "dependencies": { 361 | "ansi-regex": { 362 | "version": "2.1.1", 363 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 364 | "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", 365 | "dev": true 366 | }, 367 | "buffer-shims": { 368 | "version": "1.0.0", 369 | "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", 370 | "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==", 371 | "dev": true 372 | }, 373 | "core-util-is": { 374 | "version": "1.0.3", 375 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 376 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 377 | "dev": true 378 | }, 379 | "duplexer": { 380 | "version": "0.1.2", 381 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 382 | "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 383 | "dev": true 384 | }, 385 | "escape-string-regexp": { 386 | "version": "1.0.5", 387 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 388 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 389 | "dev": true 390 | }, 391 | "figures": { 392 | "version": "1.7.0", 393 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 394 | "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", 395 | "dev": true, 396 | "requires": { 397 | "escape-string-regexp": "^1.0.5", 398 | "object-assign": "^4.1.0" 399 | } 400 | }, 401 | "has-ansi": { 402 | "version": "2.0.0", 403 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 404 | "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", 405 | "dev": true, 406 | "requires": { 407 | "ansi-regex": "^2.0.0" 408 | } 409 | }, 410 | "inherits": { 411 | "version": "2.0.4", 412 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 413 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 414 | "dev": true 415 | }, 416 | "is-finite": { 417 | "version": "1.1.0", 418 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", 419 | "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", 420 | "dev": true 421 | }, 422 | "isarray": { 423 | "version": "1.0.0", 424 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 425 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 426 | "dev": true 427 | }, 428 | "lodash": { 429 | "version": "4.17.21", 430 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 431 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 432 | "dev": true 433 | }, 434 | "object-assign": { 435 | "version": "4.1.1", 436 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 437 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 438 | "dev": true 439 | }, 440 | "parse-ms": { 441 | "version": "1.0.1", 442 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", 443 | "integrity": "sha512-LpH1Cf5EYuVjkBvCDBYvkUPh+iv2bk3FHflxHkpCYT0/FZ1d3N3uJaLiHr4yGuMcFUhv6eAivitTvWZI4B/chg==", 444 | "dev": true 445 | }, 446 | "plur": { 447 | "version": "1.0.0", 448 | "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", 449 | "integrity": "sha512-qSnKBSZeDY8ApxwhfVIwKwF36KVJqb1/9nzYYq3j3vdwocULCXT8f8fQGkiw1Nk9BGfxiDagEe/pwakA+bOBqw==", 450 | "dev": true 451 | }, 452 | "pretty-ms": { 453 | "version": "2.1.0", 454 | "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", 455 | "integrity": "sha512-H2enpsxzDhuzRl3zeSQpQMirn8dB0Z/gxW96j06tMfTviUWvX14gjKb7qd1gtkUyYhDPuoNe00K5PqNvy2oQNg==", 456 | "dev": true, 457 | "requires": { 458 | "is-finite": "^1.0.1", 459 | "parse-ms": "^1.0.0", 460 | "plur": "^1.0.0" 461 | } 462 | }, 463 | "process-nextick-args": { 464 | "version": "1.0.7", 465 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 466 | "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==", 467 | "dev": true 468 | }, 469 | "re-emitter": { 470 | "version": "1.1.3", 471 | "resolved": "https://registry.npmjs.org/re-emitter/-/re-emitter-1.1.3.tgz", 472 | "integrity": "sha512-bHJul9CWcocrS+w5e5QrKYXV9NkbSA9hxSEyhYuctwm6keY9NXR2Xt/4A0vbMP0QvuwyfEyb4bkowYXv1ziEbg==", 473 | "dev": true 474 | }, 475 | "readable-stream": { 476 | "version": "2.2.9", 477 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", 478 | "integrity": "sha512-iuxqX7b7FYt08AriYECxUsK9KTXE3A/FenxIa3IPmvANHxaTP/wGIwwf+IidvvIDk/MsCp/oEV6A8CXo4SDcCg==", 479 | "dev": true, 480 | "requires": { 481 | "buffer-shims": "~1.0.0", 482 | "core-util-is": "~1.0.0", 483 | "inherits": "~2.0.1", 484 | "isarray": "~1.0.0", 485 | "process-nextick-args": "~1.0.6", 486 | "string_decoder": "~1.0.0", 487 | "util-deprecate": "~1.0.1" 488 | } 489 | }, 490 | "repeat-string": { 491 | "version": "1.6.1", 492 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 493 | "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", 494 | "dev": true 495 | }, 496 | "safe-buffer": { 497 | "version": "5.1.2", 498 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 499 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 500 | "dev": true 501 | }, 502 | "split": { 503 | "version": "1.0.0", 504 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", 505 | "integrity": "sha512-3SVfJe2A0WZg3D+ZEtXqYkvpSGAVaZ1MgufNCeHioBESCqQFsuT1VcQufiopBfJZqh92ZwQ6ddL378iUSbqVNQ==", 506 | "dev": true, 507 | "requires": { 508 | "through": "2" 509 | } 510 | }, 511 | "string_decoder": { 512 | "version": "1.0.3", 513 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 514 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 515 | "dev": true, 516 | "requires": { 517 | "safe-buffer": "~5.1.0" 518 | } 519 | }, 520 | "strip-ansi": { 521 | "version": "3.0.1", 522 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 523 | "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", 524 | "dev": true, 525 | "requires": { 526 | "ansi-regex": "^2.0.0" 527 | } 528 | }, 529 | "tap-out": { 530 | "version": "2.1.0", 531 | "resolved": "https://registry.npmjs.org/tap-out/-/tap-out-2.1.0.tgz", 532 | "integrity": "sha512-LJE+TBoVbOWhwdz4+FQk40nmbIuxJLqaGvj3WauQw3NYYU5TdjoV3C0x/yq37YAvVyi+oeBXmWnxWSjJ7IEyUw==", 533 | "dev": true, 534 | "requires": { 535 | "re-emitter": "1.1.3", 536 | "readable-stream": "2.2.9", 537 | "split": "1.0.0", 538 | "trim": "0.0.1" 539 | } 540 | }, 541 | "tap-spec": { 542 | "version": "5.0.0", 543 | "resolved": "https://registry.npmjs.org/tap-spec/-/tap-spec-5.0.0.tgz", 544 | "integrity": "sha512-zMDVJiE5I6Y4XGjlueGXJIX2YIkbDN44broZlnypT38Hj/czfOXrszHNNJBF/DXR8n+x6gbfSx68x04kIEHdrw==", 545 | "dev": true, 546 | "requires": { 547 | "chalk": "^1.0.0", 548 | "duplexer": "^0.1.1", 549 | "figures": "^1.4.0", 550 | "lodash": "^4.17.10", 551 | "pretty-ms": "^2.1.0", 552 | "repeat-string": "^1.5.2", 553 | "tap-out": "^2.1.0", 554 | "through2": "^2.0.0" 555 | }, 556 | "dependencies": { 557 | "ansi-styles": { 558 | "version": "2.2.1", 559 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 560 | "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", 561 | "dev": true 562 | }, 563 | "chalk": { 564 | "version": "1.1.3", 565 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 566 | "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", 567 | "dev": true, 568 | "requires": { 569 | "ansi-styles": "^2.2.1", 570 | "escape-string-regexp": "^1.0.2", 571 | "has-ansi": "^2.0.0", 572 | "strip-ansi": "^3.0.0", 573 | "supports-color": "^2.0.0" 574 | } 575 | }, 576 | "supports-color": { 577 | "version": "2.0.0", 578 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 579 | "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", 580 | "dev": true 581 | } 582 | } 583 | }, 584 | "through": { 585 | "version": "2.3.8", 586 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 587 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", 588 | "dev": true 589 | }, 590 | "through2": { 591 | "version": "2.0.5", 592 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 593 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 594 | "dev": true, 595 | "requires": { 596 | "readable-stream": "~2.3.6", 597 | "xtend": "~4.0.1" 598 | }, 599 | "dependencies": { 600 | "process-nextick-args": { 601 | "version": "2.0.1", 602 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 603 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 604 | "dev": true 605 | }, 606 | "readable-stream": { 607 | "version": "2.3.8", 608 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 609 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 610 | "dev": true, 611 | "requires": { 612 | "core-util-is": "~1.0.0", 613 | "inherits": "~2.0.3", 614 | "isarray": "~1.0.0", 615 | "process-nextick-args": "~2.0.0", 616 | "safe-buffer": "~5.1.1", 617 | "string_decoder": "~1.1.1", 618 | "util-deprecate": "~1.0.1" 619 | } 620 | }, 621 | "string_decoder": { 622 | "version": "1.1.1", 623 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 624 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 625 | "dev": true, 626 | "requires": { 627 | "safe-buffer": "~5.1.0" 628 | } 629 | } 630 | } 631 | }, 632 | "trim": { 633 | "version": "0.0.1", 634 | "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", 635 | "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", 636 | "dev": true 637 | }, 638 | "util-deprecate": { 639 | "version": "1.0.2", 640 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 641 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 642 | "dev": true 643 | }, 644 | "xtend": { 645 | "version": "4.0.2", 646 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 647 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 648 | "dev": true 649 | } 650 | } 651 | } 652 | -------------------------------------------------------------------------------- /03-native-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-esmodules", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "npx http-server .", 9 | "test": "node test/index.test.js | npx tap-spec", 10 | "test:watch": "node --watch test/index.test.js | npx tap-spec" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "tap-spec": "^5.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /03-native-tests/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://rickandmortyapi.com/api/character" 3 | } -------------------------------------------------------------------------------- /03-native-tests/src/controller.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Controller { 3 | constructor({ service, view}) { 4 | this.service = service 5 | this.view = view 6 | } 7 | static async initialize(deps) { 8 | const controller = new Controller(deps) 9 | await controller.init() 10 | } 11 | 12 | async init() { 13 | const chars = await this.service.getCharacters({ skip: 0, limit: 5 }) 14 | const data = this.prepareItems(chars) 15 | this.view.updateTable(data) 16 | } 17 | 18 | prepareItems(chars) { 19 | return chars.map(({name, image}) => { 20 | return { 21 | isBold: /smith/i.test(name), 22 | name, 23 | image, 24 | } 25 | }) 26 | } 27 | } -------------------------------------------------------------------------------- /03-native-tests/src/index.js: -------------------------------------------------------------------------------- 1 | import config from './config.json' assert { type: "json"} 2 | import Service from './service.js' 3 | import View from './view.js' 4 | 5 | import Controller from "./controller.js" 6 | 7 | await Controller.initialize({ 8 | service: new Service({ 9 | url: config.url 10 | }), 11 | view: new View() 12 | }) -------------------------------------------------------------------------------- /03-native-tests/src/service.js: -------------------------------------------------------------------------------- 1 | export default class Service { 2 | constructor({ url }) { 3 | this.url = url 4 | } 5 | async getCharacters({ skip, limit }) { 6 | const data = (await (await fetch(this.url)).json()).results 7 | return data.slice(skip, limit) 8 | } 9 | } -------------------------------------------------------------------------------- /03-native-tests/src/view.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | updateTable(items) { 3 | const output = globalThis.document.querySelector('#output') 4 | output.innerHTML = this.formatItems(items) 5 | } 6 | 7 | formatItems(items) { 8 | const toBold = ({ 9 | name, 10 | isBold 11 | }) => isBold ? `${name}` : name 12 | 13 | return items 14 | .map(item => `
        1. ${toBold(item)}
        2. `) 15 | .join("
          ") 16 | } 17 | } -------------------------------------------------------------------------------- /03-native-tests/test/index.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | it, 4 | mock 5 | } from 'node:test' 6 | import { 7 | deepStrictEqual, 8 | } from 'node:assert' 9 | 10 | import Controller from '../src/controller.js' 11 | import View from '../src/view.js' 12 | 13 | const mockedData = [{ 14 | name: 'morty smith', 15 | image: 'https://', 16 | age: 30, 17 | birthDay: new Date() 18 | }, { 19 | name: 'pickle rick', 20 | image: 'https://', 21 | age: 30, 22 | birthDay: new Date() 23 | }] 24 | describe('Unit tests for frontend', () => { 25 | it('should add a property if name contains smith and remove all other props', () => { 26 | const expected = [{ 27 | name: 'morty smith', 28 | image: 'https://', 29 | isBold: true, 30 | }, { 31 | name: 'pickle rick', 32 | image: 'https://', 33 | isBold: false, 34 | }] 35 | const controller = new Controller({ 36 | service: {}, 37 | view: {} 38 | }) 39 | const result = controller.prepareItems(mockedData) 40 | deepStrictEqual(result, expected) 41 | }) 42 | it('shoudl verify either all functions were called properly', async () => { 43 | let htmlResult = '' 44 | const globalObject = { 45 | document: { 46 | querySelector: mock.fn(() => { 47 | return { 48 | set innerHTML(value) { 49 | htmlResult = value 50 | } 51 | } 52 | }) 53 | } 54 | } 55 | globalThis = { 56 | ...globalThis, 57 | ...globalObject 58 | } 59 | 60 | const service = { 61 | getCharacters: mock.fn(() => mockedData) 62 | } 63 | 64 | const view = new View() 65 | mock.method(view, view.updateTable.name) 66 | 67 | await Controller.initialize({ 68 | service, 69 | view 70 | }) 71 | const [{ 72 | arguments: serviceCall 73 | }] = service.getCharacters.mock.calls 74 | 75 | deepStrictEqual(serviceCall, [{ 76 | skip: 0, 77 | limit: 5 78 | }]) 79 | 80 | const [{ 81 | arguments: viewCall 82 | }] = view.updateTable.mock.calls 83 | 84 | deepStrictEqual(viewCall, [ 85 | [{ 86 | isBold: true, 87 | name: 'morty smith', 88 | image: 'https://' 89 | }, 90 | { 91 | isBold: false, 92 | name: 'pickle rick', 93 | image: 'https://' 94 | } 95 | ] 96 | ]) 97 | 98 | deepStrictEqual( 99 | htmlResult, 100 | `
        3. morty smith

        4. pickle rick
        5. ` 101 | ) 102 | }) 103 | 104 | }) -------------------------------------------------------------------------------- /04-streams/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Webstream é o terroooorr 8 | 23 | 24 | 25 | 26 | 27 |
          28 |
          29 |
          30 | 31 | 32 | -------------------------------------------------------------------------------- /04-streams/app/index.js: -------------------------------------------------------------------------------- 1 | const API_URL = 'http://localhost:3000' 2 | let counter = 0 3 | 4 | async function consumeAPI(signal) { 5 | const response = await fetch(API_URL, { 6 | signal 7 | }) 8 | const reader = response.body 9 | .pipeThrough(new TextDecoderStream()) 10 | .pipeThrough(parseNDJSON()) 11 | 12 | return reader 13 | } 14 | 15 | function appendToHTML(element) { 16 | return new WritableStream({ 17 | write({ title, description, url_anime}) { 18 | const card = ` 19 |
          20 |
          21 |

          [${++counter}] ${title}

          22 |

          ${description.slice(0, 100)}

          23 | Here's why 24 |
          25 |
          26 | ` 27 | element.innerHTML += card 28 | }, 29 | abort(reason) { 30 | console.log('aborted**', reason) 31 | } 32 | }) 33 | } 34 | 35 | function parseNDJSON() { 36 | let ndjsonBuffer = '' 37 | return new TransformStream({ 38 | transform(chunk, controller) { 39 | ndjsonBuffer += chunk 40 | const items = ndjsonBuffer.split('\n') 41 | items.slice(0, -1) 42 | .forEach(item => controller.enqueue(JSON.parse(item))) 43 | 44 | ndjsonBuffer = items[items.length -1] 45 | }, 46 | flush(controller) { 47 | if(!ndjsonBuffer) return; 48 | controller.enqueue(JSON.parse(ndjsonBuffer)) 49 | } 50 | }) 51 | } 52 | const [ 53 | start, 54 | stop, 55 | cards 56 | ] = ['start', 'stop', 'cards'].map(item => document.getElementById(item)) 57 | 58 | let abortController = new AbortController() 59 | start.addEventListener('click', async () => { 60 | try { 61 | const readable = await consumeAPI(abortController.signal) 62 | // add signal and await to handle the abortError exception after abortion 63 | await readable.pipeTo(appendToHTML(cards), { signal: abortController.signal }) 64 | } catch (error) { 65 | if (!error.message.includes('abort')) throw error 66 | } 67 | }) 68 | 69 | stop.addEventListener('click', () => { 70 | abortController.abort() 71 | console.log('aborting...') 72 | abortController = new AbortController() 73 | }) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /04-streams/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx browser-sync -w .", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "browser-sync": "^2.28.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /04-streams/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04-streams", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "04-streams", 9 | "version": "0.0.1", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "concurrently": "^7.6.0" 13 | } 14 | }, 15 | "node_modules/ansi-regex": { 16 | "version": "5.0.1", 17 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 18 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 19 | "dev": true, 20 | "engines": { 21 | "node": ">=8" 22 | } 23 | }, 24 | "node_modules/ansi-styles": { 25 | "version": "4.3.0", 26 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 27 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 28 | "dev": true, 29 | "dependencies": { 30 | "color-convert": "^2.0.1" 31 | }, 32 | "engines": { 33 | "node": ">=8" 34 | }, 35 | "funding": { 36 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 37 | } 38 | }, 39 | "node_modules/chalk": { 40 | "version": "4.1.2", 41 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 42 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 43 | "dev": true, 44 | "dependencies": { 45 | "ansi-styles": "^4.1.0", 46 | "supports-color": "^7.1.0" 47 | }, 48 | "engines": { 49 | "node": ">=10" 50 | }, 51 | "funding": { 52 | "url": "https://github.com/chalk/chalk?sponsor=1" 53 | } 54 | }, 55 | "node_modules/chalk/node_modules/supports-color": { 56 | "version": "7.2.0", 57 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 58 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 59 | "dev": true, 60 | "dependencies": { 61 | "has-flag": "^4.0.0" 62 | }, 63 | "engines": { 64 | "node": ">=8" 65 | } 66 | }, 67 | "node_modules/cliui": { 68 | "version": "8.0.1", 69 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 70 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 71 | "dev": true, 72 | "dependencies": { 73 | "string-width": "^4.2.0", 74 | "strip-ansi": "^6.0.1", 75 | "wrap-ansi": "^7.0.0" 76 | }, 77 | "engines": { 78 | "node": ">=12" 79 | } 80 | }, 81 | "node_modules/color-convert": { 82 | "version": "2.0.1", 83 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 84 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 85 | "dev": true, 86 | "dependencies": { 87 | "color-name": "~1.1.4" 88 | }, 89 | "engines": { 90 | "node": ">=7.0.0" 91 | } 92 | }, 93 | "node_modules/color-name": { 94 | "version": "1.1.4", 95 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 96 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 97 | "dev": true 98 | }, 99 | "node_modules/concurrently": { 100 | "version": "7.6.0", 101 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", 102 | "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", 103 | "dev": true, 104 | "dependencies": { 105 | "chalk": "^4.1.0", 106 | "date-fns": "^2.29.1", 107 | "lodash": "^4.17.21", 108 | "rxjs": "^7.0.0", 109 | "shell-quote": "^1.7.3", 110 | "spawn-command": "^0.0.2-1", 111 | "supports-color": "^8.1.0", 112 | "tree-kill": "^1.2.2", 113 | "yargs": "^17.3.1" 114 | }, 115 | "bin": { 116 | "conc": "dist/bin/concurrently.js", 117 | "concurrently": "dist/bin/concurrently.js" 118 | }, 119 | "engines": { 120 | "node": "^12.20.0 || ^14.13.0 || >=16.0.0" 121 | }, 122 | "funding": { 123 | "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" 124 | } 125 | }, 126 | "node_modules/date-fns": { 127 | "version": "2.29.3", 128 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", 129 | "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", 130 | "dev": true, 131 | "engines": { 132 | "node": ">=0.11" 133 | }, 134 | "funding": { 135 | "type": "opencollective", 136 | "url": "https://opencollective.com/date-fns" 137 | } 138 | }, 139 | "node_modules/emoji-regex": { 140 | "version": "8.0.0", 141 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 142 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 143 | "dev": true 144 | }, 145 | "node_modules/escalade": { 146 | "version": "3.1.1", 147 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 148 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 149 | "dev": true, 150 | "engines": { 151 | "node": ">=6" 152 | } 153 | }, 154 | "node_modules/get-caller-file": { 155 | "version": "2.0.5", 156 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 157 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 158 | "dev": true, 159 | "engines": { 160 | "node": "6.* || 8.* || >= 10.*" 161 | } 162 | }, 163 | "node_modules/has-flag": { 164 | "version": "4.0.0", 165 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 166 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 167 | "dev": true, 168 | "engines": { 169 | "node": ">=8" 170 | } 171 | }, 172 | "node_modules/is-fullwidth-code-point": { 173 | "version": "3.0.0", 174 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 175 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 176 | "dev": true, 177 | "engines": { 178 | "node": ">=8" 179 | } 180 | }, 181 | "node_modules/lodash": { 182 | "version": "4.17.21", 183 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 184 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 185 | "dev": true 186 | }, 187 | "node_modules/require-directory": { 188 | "version": "2.1.1", 189 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 190 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 191 | "dev": true, 192 | "engines": { 193 | "node": ">=0.10.0" 194 | } 195 | }, 196 | "node_modules/rxjs": { 197 | "version": "7.8.0", 198 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", 199 | "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", 200 | "dev": true, 201 | "dependencies": { 202 | "tslib": "^2.1.0" 203 | } 204 | }, 205 | "node_modules/shell-quote": { 206 | "version": "1.8.0", 207 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", 208 | "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", 209 | "dev": true, 210 | "funding": { 211 | "url": "https://github.com/sponsors/ljharb" 212 | } 213 | }, 214 | "node_modules/spawn-command": { 215 | "version": "0.0.2-1", 216 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", 217 | "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", 218 | "dev": true 219 | }, 220 | "node_modules/string-width": { 221 | "version": "4.2.3", 222 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 223 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 224 | "dev": true, 225 | "dependencies": { 226 | "emoji-regex": "^8.0.0", 227 | "is-fullwidth-code-point": "^3.0.0", 228 | "strip-ansi": "^6.0.1" 229 | }, 230 | "engines": { 231 | "node": ">=8" 232 | } 233 | }, 234 | "node_modules/strip-ansi": { 235 | "version": "6.0.1", 236 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 237 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 238 | "dev": true, 239 | "dependencies": { 240 | "ansi-regex": "^5.0.1" 241 | }, 242 | "engines": { 243 | "node": ">=8" 244 | } 245 | }, 246 | "node_modules/supports-color": { 247 | "version": "8.1.1", 248 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 249 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 250 | "dev": true, 251 | "dependencies": { 252 | "has-flag": "^4.0.0" 253 | }, 254 | "engines": { 255 | "node": ">=10" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/chalk/supports-color?sponsor=1" 259 | } 260 | }, 261 | "node_modules/tree-kill": { 262 | "version": "1.2.2", 263 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 264 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 265 | "dev": true, 266 | "bin": { 267 | "tree-kill": "cli.js" 268 | } 269 | }, 270 | "node_modules/tslib": { 271 | "version": "2.5.0", 272 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", 273 | "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", 274 | "dev": true 275 | }, 276 | "node_modules/wrap-ansi": { 277 | "version": "7.0.0", 278 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 279 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 280 | "dev": true, 281 | "dependencies": { 282 | "ansi-styles": "^4.0.0", 283 | "string-width": "^4.1.0", 284 | "strip-ansi": "^6.0.0" 285 | }, 286 | "engines": { 287 | "node": ">=10" 288 | }, 289 | "funding": { 290 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 291 | } 292 | }, 293 | "node_modules/y18n": { 294 | "version": "5.0.8", 295 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 296 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 297 | "dev": true, 298 | "engines": { 299 | "node": ">=10" 300 | } 301 | }, 302 | "node_modules/yargs": { 303 | "version": "17.7.1", 304 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", 305 | "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", 306 | "dev": true, 307 | "dependencies": { 308 | "cliui": "^8.0.1", 309 | "escalade": "^3.1.1", 310 | "get-caller-file": "^2.0.5", 311 | "require-directory": "^2.1.1", 312 | "string-width": "^4.2.3", 313 | "y18n": "^5.0.5", 314 | "yargs-parser": "^21.1.1" 315 | }, 316 | "engines": { 317 | "node": ">=12" 318 | } 319 | }, 320 | "node_modules/yargs-parser": { 321 | "version": "21.1.1", 322 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 323 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 324 | "dev": true, 325 | "engines": { 326 | "node": ">=12" 327 | } 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /04-streams/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "04-streams", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "app": "cd app && npm start", 8 | "server": "cd server && npm start", 9 | "start": "concurrently 'npm run app' 'npm run server' " 10 | 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "concurrently": "^7.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /04-streams/server/index.js: -------------------------------------------------------------------------------- 1 | import { createServer } from 'node:http' 2 | import { createReadStream } from 'node:fs' 3 | import { Readable, Transform } from 'node:stream' 4 | import { WritableStream, TransformStream } from 'node:stream/web' 5 | import { setTimeout } from 'node:timers/promises' 6 | 7 | import csvtojson from 'csvtojson' 8 | const PORT = 3000 9 | // curl -i -X OPTIONS -N localhost:3000 10 | // curl -N localhost:3000 11 | createServer(async (request, response) => { 12 | const headers = { 13 | 'Access-Control-Allow-Origin': '*', 14 | 'Access-Control-Allow-Methods': '*', 15 | } 16 | 17 | if (request.method === 'OPTIONS') { 18 | response.writeHead(204, headers) 19 | response.end() 20 | return 21 | } 22 | let items = 0 23 | const abortController = new AbortController() 24 | request.once('close', _ => { 25 | console.log(`connection was closed!`, items) 26 | abortController.abort() 27 | }) 28 | try { 29 | response.writeHead(200, headers) 30 | 31 | await Readable.toWeb(createReadStream('./animeflv.csv')) 32 | // o passo a passo que cada item individual vai trafegar 33 | .pipeThrough(Transform.toWeb(csvtojson())) 34 | .pipeThrough(new TransformStream({ 35 | transform(chunk, controller) { 36 | const data = JSON.parse(Buffer.from(chunk)) 37 | const mappedData = { 38 | title: data.title, 39 | description: data.description, 40 | url_anime: data.url_anime 41 | } 42 | // quebra de linha pois é um NDJSON 43 | controller.enqueue(JSON.stringify(mappedData).concat('\n')) 44 | } 45 | })) 46 | // pipeTo é a ultima etapa 47 | .pipeTo(new WritableStream({ 48 | async write(chunk) { 49 | await setTimeout(200) 50 | items++ 51 | response.write(chunk) 52 | }, 53 | close() { 54 | response.end() 55 | } 56 | 57 | }), { 58 | signal: abortController.signal 59 | }) 60 | 61 | } catch (error) { 62 | if (!error.message.includes('abort')) throw error 63 | } 64 | 65 | }) 66 | .listen(PORT) 67 | .on('listening', _ => console.log(`server is running at ${PORT}`)) -------------------------------------------------------------------------------- /04-streams/server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "server", 9 | "version": "0.0.1", 10 | "license": "ISC", 11 | "dependencies": { 12 | "csvtojson": "^2.0.10" 13 | }, 14 | "engines": { 15 | "node": "19" 16 | } 17 | }, 18 | "node_modules/bluebird": { 19 | "version": "3.7.2", 20 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 21 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 22 | }, 23 | "node_modules/csvtojson": { 24 | "version": "2.0.10", 25 | "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", 26 | "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", 27 | "dependencies": { 28 | "bluebird": "^3.5.1", 29 | "lodash": "^4.17.3", 30 | "strip-bom": "^2.0.0" 31 | }, 32 | "bin": { 33 | "csvtojson": "bin/csvtojson" 34 | }, 35 | "engines": { 36 | "node": ">=4.0.0" 37 | } 38 | }, 39 | "node_modules/is-utf8": { 40 | "version": "0.2.1", 41 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 42 | "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" 43 | }, 44 | "node_modules/lodash": { 45 | "version": "4.17.21", 46 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 47 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 48 | }, 49 | "node_modules/strip-bom": { 50 | "version": "2.0.0", 51 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 52 | "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", 53 | "dependencies": { 54 | "is-utf8": "^0.2.0" 55 | }, 56 | "engines": { 57 | "node": ">=0.10.0" 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /04-streams/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "node --watch index.js", 9 | "start": "node index.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "engines": { 16 | "node": "19" 17 | }, 18 | "dependencies": { 19 | "csvtojson": "^2.0.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/assets/database-small.csv: -------------------------------------------------------------------------------- 1 | case,case,date cleared,call description,location,police zone,police grid,city council,x-coordinate,y-coordinate,x_gps_coords,y_gps_coords 2 | SL2016463,SL2016463,01/01/2016 12:00:00 AM,VEHICLE THEFT,5XX S 900 E ,Z5,151,4.0,1899098.0,883261.0,-111.86496146662066,40.757620896145816 3 | SL2016117,SL2016117,01/01/2016 12:00:00 AM,DOMESTIC/PHYSICAL INVESTIGATION,3XX W 300 S ,Z3,132,4.0,1889023.0,885223.0,-111.90135834600376,40.76288754694132 4 | SL2016427,SL2016427,01/01/2016 12:00:00 AM,TRESPASSING/UNWANTED - IND/SMALL GROUP,9XX N CHURCHILL DR ,Z3,131,3.0,1891429.0,895330.0,-111.89283367183442,40.790656783850956 5 | SL2016253,SL2016253,01/01/2016 12:00:00 AM,SUICIDE - THREAT,8XX W 300 N ,Z1,112,2.0,1885076.0,889825.0,-111.91568312403221,40.77546891450854 6 | SL20162,SL20162,01/01/2016 12:00:00 AM,HOLD LOG,1XX S 200 W ,Z3,132,4.0,1890028.0,886559.0,-111.89775203515366,40.76656669982441 7 | SL2016451,SL2016451,01/01/2016 12:00:00 AM,DUI DRIVER,2XX E 700 S ,Z3,135,4.0,1893682.0,881986.0,-111.8844904059753,40.754059238407514 8 | SL20164,SL20164,01/01/2016 12:00:00 AM,HOLD LOG,6XX E 500 S ,Z4,144,4.0,1897180.0,883741.0,-111.8718915856916,40.75891666684364 9 | SL20167,SL20167,01/01/2016 12:00:00 AM,HOLD LOG,1XX S MAIN ST ,Z3,133,4.0,1891897.0,886421.0,-111.89100292664365,40.766210538859816 10 | SL20168,SL20168,01/01/2016 12:00:00 AM,PHYSICAL (FIGHT) INDIVIDUAL/SMALL GROUP,16XX W 700 N ,Z1,112,1.0,1878776.0,893043.0,-111.93848548107479,40.784218588076925 11 | SL20169,SL20169,01/01/2016 12:00:00 AM,TRAFFIC STOP,1XX E 100 S ,Z3,133,4.0,1892875.0,886743.0,-111.88747748637616,40.767105984796935 12 | SL201610,SL201610,01/01/2016 12:00:00 AM,TRAFFIC STOP,,113,,,,,, 13 | SL20166,SL20166,01/01/2016 12:00:00 AM,CELL 911 HANGUP,8XX W 100 S ,Z2,121,2.0,1885393.0,886772.0,-111.91448753269357,40.76709359244312 14 | SL201615,SL201615,01/01/2016 12:00:00 AM,HOLD LOG,1XX S MAIN ST ,Z3,133,4.0,1891897.0,886421.0,-111.89100292664365,40.766210538859816 15 | SL201617,SL201617,01/01/2016 12:00:00 AM,HOLD LOG,1XX S 200 W ,Z3,132,4.0,1890028.0,886559.0,-111.89775203515366,40.76656669982441 16 | SL201618,SL201618,01/01/2016 12:00:00 AM,HOLD LOG,0XX W 300 S ,Z3,133,4.0,1891718.0,885293.0,-111.89163126027671,40.763112433818634 17 | SL201623,SL201623,01/01/2016 12:00:00 AM,HOLD LOG,,SOSL,SOSL,,1887422.0,869203.0,-111.9068745235229,40.71889818951389 18 | SL201611,SL201611,01/01/2016 12:00:00 AM,TRAFFIC STOP,3XX W PAXTON AVE ,Z5,152,5.0,1889383.0,878176.0,-111.89994509279057,40.74355040073993 19 | SL201621,SL201621,01/01/2016 12:00:00 AM,911 HANGUP CALL,1XX E WESTMINSTER AVE ,Z6,161,5.0,1893124.0,872787.0,-111.88636102134953,40.72880447201603 20 | SL201622,SL201622,01/01/2016 12:00:00 AM,HOLD LOG,2XX N 2200 W ,Z1,113,1.0,1874833.0,889644.0,-111.95266088662085,40.774835770400095 21 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 14 | 15 | 16 | 17 |
          18 |
          19 | 20 | 21 |
          22 |
          23 | 24 | 25 |
          26 | 27 |
          28 | 29 | 30 |
          31 |
          32 | 33 | 34 |
          35 | 36 |
          37 | 38 |
          39 | 40 |
          41 | 42 | 0% 43 |
          44 | 45 |
          46 | 47 |
          48 | 49 |
          50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recorded", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx browser-sync -w" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "browser-sync": "^2.27.11" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/src/controller.js: -------------------------------------------------------------------------------- 1 | export default class Controller { 2 | #view 3 | #worker 4 | #service 5 | #events = { 6 | alive: () => { }, 7 | progress: ({ total }) => { 8 | this.#view.updateProgress(total) 9 | }, 10 | ocurrenceUpdate: ({ found, linesLength, took }) => { 11 | const [[key, value]] = Object.entries(found) 12 | this.#view.updateDebugLog( 13 | `found ${value} ocurrencies of ${key} - over ${linesLength} lines - took: ${took}` 14 | ) 15 | } 16 | } 17 | 18 | constructor({ view, worker, service }) { 19 | this.#view = view 20 | this.#service = service 21 | this.#worker = this.#configureWorker(worker) 22 | } 23 | 24 | static init(deps) { 25 | const controller = new Controller(deps) 26 | controller.init() 27 | return controller 28 | } 29 | 30 | init() { 31 | this.#view.configureOnFileChange( 32 | this.#configureOnFileChange.bind(this) 33 | ) 34 | 35 | this.#view.configureOnFormSubmit( 36 | this.#configureOnFormSubmit.bind(this) 37 | ) 38 | } 39 | 40 | #configureWorker(worker) { 41 | worker.onmessage = ({ data }) => this.#events[data.eventType](data) 42 | 43 | return worker 44 | } 45 | 46 | #formatBytes(bytes) { 47 | const units = ['B', 'KB', 'MB', 'GB', 'TB'] 48 | 49 | let i = 0 50 | 51 | for (i; bytes >= 1024 && i < 4; i++) { 52 | bytes /= 1024 53 | } 54 | 55 | return `${bytes.toFixed(2)} ${units[i]}` 56 | } 57 | 58 | #configureOnFileChange(file) { 59 | this.#view.setFileSize( 60 | this.#formatBytes(file.size) 61 | ) 62 | } 63 | 64 | #configureOnFormSubmit({ description, file }) { 65 | const query = {} 66 | query['call description'] = new RegExp( 67 | description, 'i' 68 | ) 69 | 70 | if (this.#view.isWorkerEnabled()) { 71 | console.log('executing on worker thread!') 72 | this.#worker.postMessage({ query, file }) 73 | return 74 | } 75 | 76 | console.log('executing on main thread!') 77 | this.#service.processFile({ 78 | query, 79 | file, 80 | onProgress: (total) => { 81 | this.#events.progress({ total }) 82 | }, 83 | onOcurrenceUpdate: (...args) => { 84 | this.#events.ocurrenceUpdate(...args) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/src/index.js: -------------------------------------------------------------------------------- 1 | import Controller from "./controller.js" 2 | import Service from "./service.js" 3 | import View from "./view.js" 4 | 5 | // worker modules só funciona no chrome por enquanto 6 | // ou seja worker funciona, mas import/export nao 7 | const worker = new Worker('./src/worker.js', { 8 | type: "module" 9 | }) 10 | 11 | 12 | Controller.init({ 13 | view: new View(), 14 | service: new Service(), 15 | worker 16 | }) -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/src/service.js: -------------------------------------------------------------------------------- 1 | export default class Service { 2 | 3 | processFile({ query, file, onOcurrenceUpdate, onProgress }) { 4 | const linesLength = { counter: 0 } 5 | const progressFn = this.#setupProgress(file.size, onProgress) 6 | const startedAt = performance.now() 7 | const elapsed = () => `${((performance.now() - startedAt) / 1000).toFixed(2)} secs` 8 | 9 | const onUpdate = () => { 10 | return (found) => { 11 | onOcurrenceUpdate({ 12 | found, 13 | took: elapsed(), 14 | linesLength: linesLength.counter 15 | }) 16 | 17 | } 18 | } 19 | 20 | file.stream() 21 | .pipeThrough(new TextDecoderStream()) 22 | .pipeThrough(this.#csvToJSON({ linesLength, progressFn })) 23 | .pipeTo(this.#findOcurrencies({ query, onOcurrenceUpdate: onUpdate() })) 24 | // .pipeTo(new WritableStream({ 25 | // write(chunk) { 26 | // console.log('chunk', chunk) 27 | // } 28 | // })) 29 | } 30 | 31 | #csvToJSON({ linesLength, progressFn }) { 32 | let columns = [] 33 | return new TransformStream({ 34 | transform(chunk, controller) { 35 | progressFn(chunk.length) 36 | const lines = chunk.split('\n') 37 | linesLength.counter += lines.length 38 | 39 | if (!columns.length) { 40 | const firstLine = lines.shift() 41 | columns = firstLine.split(',') 42 | linesLength.counter-- 43 | } 44 | 45 | for (const line of lines) { 46 | if (!line.length) continue 47 | let currentItem = {} 48 | const currentColumsItems = line.split(',') 49 | for (const columnIndex in currentColumsItems) { 50 | const columnItem = currentColumsItems[columnIndex] 51 | currentItem[columns[columnIndex]] = columnItem.trimEnd() 52 | } 53 | controller.enqueue(currentItem) 54 | } 55 | } 56 | }) 57 | } 58 | 59 | #findOcurrencies({ query, onOcurrenceUpdate }) { 60 | const queryKeys = Object.keys(query) 61 | let found = {} 62 | 63 | return new WritableStream({ 64 | write(jsonLine) { 65 | for (const keyIndex in queryKeys) { 66 | const key = queryKeys[keyIndex] 67 | const queryValue = query[key] 68 | found[queryValue] = found[queryValue] ?? 0 69 | if (queryValue.test(jsonLine[key])) { 70 | found[queryValue]++ 71 | onOcurrenceUpdate(found) 72 | } 73 | } 74 | }, 75 | close: () => onOcurrenceUpdate(found) 76 | }) 77 | } 78 | #setupProgress(totalBytes, onProgress) { 79 | let totalUploaded = 0 80 | onProgress(0) 81 | 82 | return (chunkLength) => { 83 | totalUploaded += chunkLength 84 | const total = 100 / totalBytes * totalUploaded 85 | onProgress(total) 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/src/view.js: -------------------------------------------------------------------------------- 1 | export default class View { 2 | #csvFile = document.querySelector('#csv-file') 3 | #fileSize = document.querySelector('#file-size') 4 | #form = document.querySelector('#form') 5 | #debug = document.querySelector('#debug') 6 | #progress = document.querySelector('#progress') 7 | #worker = document.querySelector('#worker') 8 | 9 | setFileSize(size) { 10 | this.#fileSize.innerText = `File size: ${size}\n` 11 | } 12 | 13 | configureOnFileChange(fn) { 14 | this.#csvFile.addEventListener('change', e => { 15 | fn(e.target.files[0]) 16 | }) 17 | } 18 | 19 | configureOnFormSubmit(fn) { 20 | this.#form.reset() 21 | this.#form.addEventListener('submit', (e) => { 22 | e.preventDefault() 23 | const file = this.#csvFile.files[0] 24 | // isso aqui deveria estar na controller 25 | if(!file) { 26 | alert('Please select a file!') 27 | return 28 | } 29 | this.updateDebugLog("") 30 | const form = new FormData(e.currentTarget) 31 | const description = form.get('description') 32 | fn({ description, file }) 33 | }) 34 | } 35 | 36 | updateDebugLog(text, reset = true) { 37 | if(reset) { 38 | this.#debug.innerText = text 39 | return; 40 | } 41 | 42 | this.#debug.innerText += text 43 | } 44 | 45 | updateProgress(value) { 46 | this.#progress.value = value 47 | } 48 | 49 | isWorkerEnabled() { 50 | return this.#worker.checked 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /05-multithreading-in-the-browser/src/worker.js: -------------------------------------------------------------------------------- 1 | import Service from './service.js' 2 | console.log(`I'm alive!`) 3 | const service = new Service() 4 | 5 | postMessage({ eventType: 'alive' }) 6 | onmessage = ({ data }) => { 7 | const { query, file } = data 8 | service.processFile({ 9 | query, 10 | file, 11 | onOcurrenceUpdate: (args) => { 12 | postMessage({ eventType: 'ocurrenceUpdate', ...args }) 13 | }, 14 | onProgress: (total) => postMessage({ eventType: 'progress', total }) 15 | }) 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 5 golden lessons about Node.js that apply to the frontend development (or any environment) 2 | 3 | - Examples of my talk given in Brazilian Portuguese at [#DevPR](https://devpr.org/#/) at 11/03/2023 4 | - Access the [slides presentation (pt-br)](https://bit.ly/5-aprendizados-backend-ew-talk) 5 | 6 | Screen Shot 2023-03-11 at 08 53 14 7 | -------------------------------------------------------------------------------- /annotations.txt: -------------------------------------------------------------------------------- 1 | Intro 2 | - Nada a ver com frameworks ou libs como React, Angular, Vue e outros 3 | - Minhas opiniões e conclusões, não necessariamente sou o dono da verdade 4 | - Quem ai gosta de live coding? 5 | - Tenho costume de fazer palestras de 30 minutos 6 | - VocÊ é dev JavaScript front ou backend? 7 | - você é dev JavaScript 8 | - um jogo que roda no navegador e tem lógica, é o back end 9 | - as rotas de uma API, é o frontend 10 | - frontend tem a ver com o que usuario é exposto, se você fornece uma api, é o backend 11 | 12 | 1 - ECMAScript Modules - pode isar import, export, toplevel await etc 13 | - brief story of ECMAScript Modules 14 | - Gil tayar 15 | - index.html com um monte de import 16 | - todos imports vindo do index.js 17 | - import json direto do index.js 18 | - Possibilidade de reutilizar código entre node e browser 19 | https://github.com/ErickWendel/lives-aquecimento03-javascript-expert/tree/main/01-esmodules 20 | - não precisa mais de window.onload 21 | 22 | https://stackoverflow.com/questions/17653384/refused-to-execute-inline-script-because-it-violates-the-following-content-secur 23 | https://www.youtube.com/live/Z_ZKGX8-lPw?feature=share 24 | 25 | 2 - Separação de responsabilidades (criar pastinhas) 26 | - JS não é bagunça 27 | - controller, view, indx 28 | - Injeção de dependencias - facilita testes 29 | - Padrão Factory 30 | 31 | 3 - Testes unitários + Stubs, Mocks e tecnicas de testes - mock do window - mostrar piramide de testes 32 | - vou te mostrar como testar sem instalar nada 33 | - https://github.com/nodejs/node/pull/45608 34 | 35 | 4 - Processamento de dados - Web Streams 36 | - Nem sempre request e response é o ideal 37 | - Consumir informações sob demanda 38 | - Produzir dados sob demanda 39 | - Como ler 10GB de JSON no frontend sem travar a tela 40 | - https://youtu.be/-IpRYbL4yMk 41 | - Profilling 42 | - https://yonatankra.com/how-to-profile-javascript-performance-in-the-browser/ 43 | 44 | 5 - Multithreading & Web Assembly 45 | - JavaScript suporta multithreading? 46 | - https://www.youtube.com/live/f7MY2OtI7nA?feature=share 47 | - Web workers e module workers 48 | - https://www.youtube.com/live/-wXPxJYhZeI?feature=share 49 | - Machine learning 50 | - colocar projeto do hands aqui 51 | - Processamento de dados na máquina dos outros 52 | https://ffmpegwasm.netlify.app/#demo 53 | https://github.com/ffmpegwasm/ffmpeg.wasm 54 | --------------------------------------------------------------------------------