├── vercel.json ├── playwright ├── README.md ├── .eslintrc.js └── ui │ └── landingPages.play.js ├── static └── img │ ├── favicon.ico │ ├── typescript-error.png │ ├── relay-new-devtools.png │ ├── graphql-response-overfetching.png │ ├── undraw_docusaurus_tree.svg │ └── logo.svg ├── til ├── 2019-07-18-a-b-vs-ancestry-path.md ├── 2019-01-25-git-changes-last-week.md ├── 2019-02-28-does-it-mutate.md ├── 2022-04-03-list-of-all-packages-installed-using-homebrew.md ├── 2019-07-29-4b825dc642cb6eb9a060e54bf8d69288fbee4904.md ├── 2019-04-17-sleep-sort.md ├── 2021-02-01-asserting-a-specific-enum-variant.md ├── 2019-09-18-beyond-console-log.md ├── 2019-01-28-is-object.md ├── 2019-11-06-flow-spreads-dont-preserve-read-only-ness.md ├── 2019-01-10-clearing-resetting-restoring-jest-mocks.md ├── 2019-09-30-rem-units.md ├── 2021-07-04-high-color-contrast-via-css.md ├── 2019-08-19-nodejs-lts-or-not.md ├── 2019-03-29-yarn-comments-in-package-json.md ├── 2019-06-24-optional-chaining-gotchas.md ├── 2019-07-09-splitting-string.md ├── 2019-12-30-responsive-component-with-hooks.md ├── 2022-02-16-new-relay-flow-type-names.md ├── 2020-01-31-graphql-rate-limiting-cost-computation.md ├── 2022-02-23-macos-monterey-blocking-port-5000.md ├── 2018-12-14-getters-can-be-dangerous.md ├── 2022-03-06-math-imul.md ├── 2022-02-15-using-dev-constant-in-rescript.md ├── 2019-09-30-computing-average-in-a-constant-time.md ├── 2020-07-21-strictequals-without-equals-operators.md ├── 2019-08-19-gitignore.md ├── 2019-05-22-v8-built-in-functions.md ├── 2020-02-10-css-selectors.md ├── 2019-09-26-understanding-git-diff.md ├── 2019-09-30-identify-css-layout-inconsistencies.md ├── 2019-09-26-object-preventextensions-seal-freeze.md ├── 2019-01-28-is-this-thing-a-number.md ├── 2019-09-26-placement-of-catch-before-and-after-then.md └── 2022-03-12-turbofish.md ├── babel.config.js ├── .eslintrc.js ├── .gitignore ├── README.md ├── src ├── pages │ ├── index.js │ └── styles.module.css └── css │ └── custom.css ├── package.json ├── playwright.config.js ├── sidebars.js ├── Dockerfile ├── docs ├── relay │ ├── match-module.md │ ├── uploadables.md │ └── local-schema.md ├── flow │ ├── patterns │ │ └── exhaustive-checking.md │ ├── configuration.md │ ├── unsealed-objects.md │ ├── shenanigans.md │ ├── debugging.md │ └── saved-state.md ├── git.md ├── rust.md ├── graphql.md ├── relay.md └── flow.md ├── til-articles ├── 2019-10-14-flow-react-restricted-element.md ├── 2020-12-28-understanding-rust-modules.md └── 2019-10-14-flow-new-spread-model.md └── docusaurus.config.js /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "silent": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /playwright/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | yarn workspace mrtnzlml-meta playwright test 3 | ``` 4 | -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrtnzlml/meta/HEAD/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/typescript-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrtnzlml/meta/HEAD/static/img/typescript-error.png -------------------------------------------------------------------------------- /static/img/relay-new-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrtnzlml/meta/HEAD/static/img/relay-new-devtools.png -------------------------------------------------------------------------------- /static/img/graphql-response-overfetching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrtnzlml/meta/HEAD/static/img/graphql-response-overfetching.png -------------------------------------------------------------------------------- /til/2019-07-18-a-b-vs-ancestry-path.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'A..B vs A...B vs --ancestry-path' 3 | tags: ['git'] 4 | --- 5 | 6 | See: https://stackoverflow.com/a/36437843 7 | -------------------------------------------------------------------------------- /til/2019-01-25-git-changes-last-week.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Git: What happened in last week?' 3 | tags: ['git'] 4 | --- 5 | 6 | ```bash 7 | git log --since=1.week --oneline --no-merges 8 | ``` 9 | -------------------------------------------------------------------------------- /til/2019-02-28-does-it-mutate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Does it mutate? 3 | tags: ['javascript'] 4 | --- 5 | 6 | Which JavaScript functions mutate and which don't? 7 | 8 | - https://doesitmutate.xyz/ 9 | - https://stackoverflow.com/a/9009934/3135248 10 | -------------------------------------------------------------------------------- /til/2022-04-03-list-of-all-packages-installed-using-homebrew.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: List of all packages installed using Homebrew 3 | --- 4 | 5 | ```text 6 | brew leaves | xargs -n1 brew desc 7 | ``` 8 | 9 | Source: https://apple.stackexchange.com/a/154750/436493 10 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ft-flow/require-valid-file-annotation */ 2 | 3 | module.exports = function (api) { 4 | api.assertVersion(7); 5 | api.cache(true); 6 | 7 | return { 8 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /til/2019-07-29-4b825dc642cb6eb9a060e54bf8d69288fbee4904.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: '4b825dc642cb6eb9a060e54bf8d69288fbee4904' 3 | --- 4 | 5 | ``` 6 | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 7 | ``` 8 | 9 | This hash exists in every Git repository: https://stackoverflow.com/q/9765453/3135248 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'hermes-eslint', 6 | env: { 7 | node: true, 8 | }, 9 | extends: [ 10 | '@adeira/eslint-config/base', 11 | '@adeira/eslint-config/flowtype', 12 | '@adeira/eslint-config/react', 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /til/2019-04-17-sleep-sort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sleep sort in JavaScript 3 | tags: ['javascript'] 4 | --- 5 | 6 | 🤪 🤨 🙃 7 | 8 | ```js 9 | [3, 5, 1, 8, 2, 4, 9, 6, 7].forEach((num) => { 10 | setTimeout(() => console.log(num), num); 11 | }); 12 | ``` 13 | 14 | https://twitter.com/JavaScriptDaily/status/856267407106682880 15 | -------------------------------------------------------------------------------- /playwright/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | 3 | /* eslint-disable no-unused-vars */ 4 | const OFF = 0; 5 | const WARN = 1; 6 | const ERROR = 2; 7 | /* eslint-enable no-unused-vars */ 8 | 9 | module.exports = { 10 | rules: { 11 | // Jest rules incompatible with Playwright: 12 | 'jest/no-disabled-tests': OFF, 13 | 'jest/no-standalone-expect': OFF, 14 | 'jest/valid-title': OFF, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /til/2021-02-01-asserting-a-specific-enum-variant.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Asserting a specific enum variant' 3 | tags: ['rust'] 4 | --- 5 | 6 | Specifically, asserting that result is a specific variant of an enum of structs when we don't care about the fields (something like "is instance of"): 7 | 8 | ```rust 9 | assert!(matches!(return_with_fields(), MyEnum::WithFields { .. })); 10 | ``` 11 | 12 | Source: https://stackoverflow.com/a/51123901/3135248 13 | -------------------------------------------------------------------------------- /til/2019-09-18-beyond-console-log.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Beyond console.log() 3 | tags: ['javascript'] 4 | --- 5 | 6 | There is more than just a simple `console.log`: https://medium.com/@mattburgess/beyond-console-log-2400fdf4a9d8 7 | 8 | - `console.log/warn/error/info` with `%s`, `%o` and `%c` 9 | - `console.dir` 10 | - `console.table` 11 | - `console.assert` 12 | - `console.count/countReset` 13 | - `console.trace` 14 | - `console.time/timeEnd` 15 | - `console.group/groupCollapsed/groupEnd` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # production 5 | /build 6 | 7 | # generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # misc 12 | .DS_Store 13 | .idea 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Yarn Berry (without Zero-installs) 24 | .pnp.* 25 | .yarn/* 26 | !.yarn/patches 27 | !.yarn/plugins 28 | !.yarn/releases 29 | !.yarn/sdks 30 | !.yarn/versions 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hi! 👋 I am Martin Zlámal and this is where I store my knowledge: https://mrtnzlml.com/ 2 | 3 | ## Contributing 4 | 5 | Do you want to improve this website? This is how you install it: 6 | 7 | ```text 8 | git clone git@github.com:mrtnzlml/meta.git 9 | cd meta 10 | yarn install 11 | yarn start 12 | ``` 13 | 14 | Now, make some changes and send a [pull request](https://help.github.com/en/articles/about-pull-requests). That's it. :) 15 | 16 | ![Thank you](https://media.giphy.com/media/KJ1f5iTl4Oo7u/giphy-downsized-large.gif) 17 | -------------------------------------------------------------------------------- /til/2019-01-28-is-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: isObject() 3 | tags: ['javascript'] 4 | --- 5 | 6 | ```js 7 | function isObject(value): boolean %checks { 8 | return typeof value === 'object' && value !== null && !Array.isArray(value); 9 | } 10 | ``` 11 | 12 | Jest [implementation](https://github.com/facebook/jest/blob/d7ca8b23acf2fdd1d070496efb2b2709644a6f4f/packages/jest-snapshot/src/utils.js#L79-L81): 13 | 14 | ```js 15 | function isObject(item) { 16 | return item && typeof item === 'object' && !Array.isArray(item); 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /til/2019-11-06-flow-spreads-dont-preserve-read-only-ness.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Flow: spreads don't preserve read-only-ness" 3 | tags: ['javascript', 'flow'] 4 | --- 5 | 6 | ```js 7 | type A = {| +readOnlyKey: string |}; 8 | type B = {| ...A, +otherKey: string |}; 9 | 10 | function test(x: B) { 11 | x.readOnlyKey = 'overwrite'; // no error ? 12 | x.otherKey = 'overwrite'; // no error ?? 13 | } 14 | ``` 15 | 16 | This applies to value spreads as well since they are creating a new object. It's less understandable for these type spreads where value spread is not involved. 17 | -------------------------------------------------------------------------------- /til/2019-01-10-clearing-resetting-restoring-jest-mocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clearing/resetting/restoring Jest mocks 3 | tags: ['javascript', 'jest'] 4 | --- 5 | 6 | I am never gonna remember this correctly I guess. 🤷 7 | 8 | - `jest.clearAllMocks()` only clears the internal state of the mock 9 | - `jest.resetAllMocks()` does the same + it removes any mocked implementations or return values 10 | - `jest.restoreAllMocks()` does everything above but it restores the original non-mocked implementation (and works only with `jest.spyOn`) 11 | 12 | https://github.com/facebook/jest/issues/5143 13 | -------------------------------------------------------------------------------- /til/2019-09-30-rem-units.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: REM units 3 | tags: ['css'] 4 | --- 5 | 6 | ```css 7 | html { 8 | font-size: 6.25%; /* =1px */ 9 | /* Since most browsers have a default value of 16px. Alternatively, people quite often 10 | use 62.5% instead and adjust children REM units accordingly. */ 11 | } 12 | body { 13 | font-size: 14rem; /* =14px */ 14 | } 15 | h1 { 16 | font-size: 24rem; /* =24px */ 17 | } 18 | ``` 19 | 20 | Default HTML font size: https://stackoverflow.com/questions/24542508/default-html-font-size 21 | 22 | Even better approach: https://css-tricks.com/rems-ems/ 23 | -------------------------------------------------------------------------------- /til/2021-07-04-high-color-contrast-via-css.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: High color contrast via CSS 3 | tags: ['css'] 4 | --- 5 | 6 | See: https://css-tricks.com/css-variables-calc-rgb-enforcing-high-contrast-colors/ 7 | 8 | ```css 9 | :root { 10 | --red: 28; 11 | --green: 150; 12 | --blue: 130; 13 | 14 | --accessible-color: calc( 15 | ((((var(--red) * 299) + (var(--green) * 587) + (var(--blue) * 114)) / 1000) - 128) * -1000 16 | ); 17 | } 18 | 19 | .button { 20 | color: rgb(var(--accessible-color), var(--accessible-color), var(--accessible-color)); 21 | background-color: rgb(var(--red), var(--green), var(--blue)); 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /til/2019-08-19-nodejs-lts-or-not.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Should you use Node.js LTS or not? 3 | tags: ['javascript'] 4 | --- 5 | 6 | > Node LTS is primarily aimed at enterprise use where there may be more resistance to frequent updates, extensive procurement procedures and lengthy test and quality requirements. 7 | 8 | > Generally if you are able to keep up with the latest stable and future Node releases you should do so. These are stable and _production ready_ releases with excellent community support. Unstable and experimental functionality is kept behind build and runtime flags and should not affect your day to day operations. 9 | 10 | https://stackoverflow.com/a/34655149/3135248 11 | -------------------------------------------------------------------------------- /til/2019-03-29-yarn-comments-in-package-json.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yarn comments in package.json 3 | tags: ['yarn'] 4 | --- 5 | 6 | ```json 7 | { 8 | "private": true, 9 | "devDependencies": { 10 | "//": [ 11 | "Please note: `react` dependency here is necessary in order to solve hoisting issues", 12 | "with React Native (Expo) and their locked React version. Yarn hoisted wrong version.", 13 | "It can eventually be removed (try Relay and RN-Expo examples to verify it works)." 14 | ], 15 | "flow-bin": "^0.95.1", 16 | "react": "^16.8.6" 17 | } 18 | } 19 | ``` 20 | 21 | https://github.com/yarnpkg/yarn/pull/3829/files (also great example of `test.concurrent` usage ^^) 22 | -------------------------------------------------------------------------------- /til/2019-06-24-optional-chaining-gotchas.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Optional chaining gotchas 3 | tags: ['javascript'] 4 | --- 5 | 6 | Optional chaining != error suppression operator. 7 | 8 | ```js 9 | (function () { 10 | 'use strict'; 11 | undeclared_var?.b; // ReferenceError: undeclared_var is not defined 12 | arguments?.callee; // TypeError: 'callee' may not be accessed in strict mode 13 | arguments.callee?.(); // TypeError: 'callee' may not be accessed in strict mode 14 | true?.(); // TypeError: true is not a function 15 | })(); 16 | ``` 17 | 18 | - https://v8.dev/features/optional-chaining 19 | - https://github.com/tc39/proposal-optional-chaining/commit/87e408d375bd749b21d70e65bd0cbbf57d9bcf82 20 | -------------------------------------------------------------------------------- /til/2019-07-09-splitting-string.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to split strings in JavaScript 3 | tags: ['javascript'] 4 | --- 5 | 6 | ```js 7 | 'I 💖 U'.split(' '); // ✅: [ 'I', '💖', 'U' ] 8 | 'I💖U'.split(''); // ❌: [ 'I', '�', '�', 'U' ] 9 | ``` 10 | 11 | Better alternatives: 12 | 13 | ```js 14 | [...'I💖U']; 15 | Array.from('I💖U'); 16 | 'I💖U'.split(/(?=[\s\S])/u); 17 | ``` 18 | 19 | More info: [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split), [stackoverflow.com](https://stackoverflow.com/a/34717402/3135248) 20 | 21 | Please note - it's still a bit more complicated. Read this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#reversing_a_string_using_split 22 | -------------------------------------------------------------------------------- /til/2019-12-30-responsive-component-with-hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Responsive component with hooks 3 | tags: ['react'] 4 | --- 5 | 6 | ```js 7 | function MyResponsiveComponent() { 8 | const width = useWindowWidth(); // Our custom Hook 9 | return

Window width is {width}

; 10 | } 11 | ``` 12 | 13 | ```js 14 | function useWindowWidth() { 15 | const [width, setWidth] = useState(window.innerWidth); 16 | 17 | useEffect(() => { 18 | const handleResize = () => setWidth(window.innerWidth); 19 | window.addEventListener('resize', handleResize); 20 | return () => { 21 | window.removeEventListener('resize', handleResize); 22 | }; 23 | }); 24 | 25 | return width; 26 | } 27 | ``` 28 | 29 | Source: https://gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae 30 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @flow 8 | */ 9 | 10 | import React from 'react'; 11 | /* $FlowFixMe[cannot-resolve-module] This comment suppresses an error when 12 | * merging two repositories. To see the error delete this comment and run Flow. */ 13 | import { Redirect } from '@docusaurus/router'; // eslint-disable-line import/no-unresolved 14 | 15 | /* $FlowFixMe[signature-verification-failure] This comment suppresses an error when 16 | * merging two repositories. To see the error delete this comment and run Flow. */ 17 | export default function Home() { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /til/2022-02-16-new-relay-flow-type-names.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Flow types for Relay 3 | tags: ['relay', 'flow'] 4 | --- 5 | 6 | Relay version 13 shipped with a new Rust Compiler and requires migration to the new Flow type names. @kassens described the migration path well in this comment: https://github.com/facebook/relay/issues/3792#issuecomment-1033933397 7 | 8 | - `SomeFragment$data` (instead of `SomeFragment` (no suffix)) 9 | - `SomeFragment$fragmentType` (instead of `SomeFragment$ref`) 10 | - `SomeQuery$variables` (instead of `SomeQueryVariables`) 11 | - `SomeQuery$data` (instead of `SomeQueryResponse`, as it works like the `$data` for fragments and isn't the full response) 12 | - `$fragmentSpreads` (as a key in generated files instead of `$refs`) 13 | 14 | I also asked about the new Flow types here: https://github.com/facebook/relay/issues/3717 15 | -------------------------------------------------------------------------------- /til/2020-01-31-graphql-rate-limiting-cost-computation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'GraphQL: Rate Limiting, Cost Computation' 3 | tags: ['graphql'] 4 | --- 5 | 6 | So far the best idea I've ever seen is this one: https://github.com/adeira/universe/blob/5d2c15e1767a6e91c5eb82f41abc1e856811d0df/src/graphql-result-size/semantics-and-complexity-of-graphql.pdf (alternative reading: [Result size calculation for Facebook’s GraphQL query language](https://www.diva-portal.org/smash/get/diva2:1237221/FULLTEXT01.pdf)) 7 | 8 | Experimental implementation here: https://github.com/adeira/universe/tree/5d2c15e1767a6e91c5eb82f41abc1e856811d0df/src/graphql-result-size 9 | 10 | Alternative approaches: 11 | 12 | - https://developer.github.com/v4/guides/resource-limitations/ (TODO: explain why it's worse and when you should consider it) 13 | - https://twitter.com/__xuorig__/status/1148653318069207041 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mrtnzlml-meta", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "docusaurus": "docusaurus", 8 | "start": "docusaurus start", 9 | "build": "docusaurus build", 10 | "swizzle": "docusaurus swizzle", 11 | "deploy": "docusaurus deploy" 12 | }, 13 | "dependencies": { 14 | "@docusaurus/core": "^3.6.3", 15 | "@docusaurus/preset-classic": "^3.6.3", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1" 18 | }, 19 | "devDependencies": { 20 | "@playwright/test": "^1.45.0" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /** 9 | * CSS files with the .module.css suffix will be treated as CSS modules 10 | * and scoped locally. 11 | */ 12 | 13 | .heroBanner { 14 | padding: 4rem 0; 15 | text-align: center; 16 | position: relative; 17 | overflow: hidden; 18 | } 19 | 20 | @media screen and (max-width: 966px) { 21 | .heroBanner { 22 | padding: 2rem; 23 | } 24 | } 25 | 26 | .buttons { 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | .features { 33 | display: flex; 34 | align-items: center; 35 | padding: 2rem 0; 36 | width: 100%; 37 | } 38 | 39 | .featureImage { 40 | height: 200px; 41 | width: 200px; 42 | } 43 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line ft-flow/require-valid-file-annotation,import/no-extraneous-dependencies 2 | const { devices, defineConfig } = require('@playwright/test'); 3 | 4 | export default defineConfig({ 5 | timeout: 60000, 6 | testDir: 'playwright', 7 | outputDir: 'playwright/test-results', 8 | testMatch: '**.play.js', 9 | forbidOnly: !!process.env.CI, 10 | reporter: process.env.CI ? 'github' : 'list', 11 | retries: process.env.CI ? 2 : 0, 12 | webServer: { 13 | command: process.env.CI ? 'yarn build && yarn start' : 'yarn start', 14 | port: 3000, 15 | timeout: 120 * 1000, // milliseconds 16 | reuseExistingServer: !process.env.CI, 17 | }, 18 | use: { 19 | trace: 'on-first-retry', 20 | screenshot: 'only-on-failure', 21 | }, 22 | projects: [ 23 | { 24 | name: 'Desktop Chrome', 25 | use: devices['Desktop Chrome'], 26 | }, 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /til/2022-02-23-macos-monterey-blocking-port-5000.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: macOS Monterey is blocking port 5000 3 | tags: ['macos'] 4 | --- 5 | 6 | This happened today. I couldn't start my Rust server because port 5000 was already occupied. But by whom? 🤔 I did some digging, and I got something like this: 7 | 8 | ```text 9 | $ lsof -i :5000 10 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 11 | ControlCe 1677 user 32u IPv4 0x728ff8e52d51c6dd 0t0 TCP *:commplex-main (LISTEN) 12 | ControlCe 1677 user 33u IPv6 0x728ff8e51d98ec65 0t0 TCP *:commplex-main (LISTEN) 13 | ``` 14 | 15 | Hmm? I tried to kill the process but that didn't help. It took me a while to research that the culprit is macOS Monterey (I didn't start the server ever since I upgraded to this macOS version): https://stackoverflow.com/q/69868760/3135248 16 | 17 | Solution: Go to System Preference --> Sharing --> uncheck off the "AirPlay Receiver" 18 | 19 | WTF 20 | -------------------------------------------------------------------------------- /til/2018-12-14-getters-can-be-dangerous.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getters can be dangerous 3 | tags: ['javascript'] 4 | --- 5 | 6 | ```js 7 | // @flow 8 | 9 | type x = { 10 | +address: ?{| 11 | +fullAddress: ?string, 12 | |}, 13 | }; 14 | 15 | class WTF { 16 | _address = { 17 | fullAddress: 'yay', 18 | }; 19 | 20 | get address() { 21 | const addr = this._address; 22 | this._address = null; 23 | return addr; 24 | } 25 | } 26 | 27 | const y = new WTF(); 28 | 29 | // this is going to explode: 30 | console.warn(y.address?.fullAddress && y.address.fullAddress); 31 | 32 | // here is why: 33 | // console.warn( 34 | // y.address, 35 | // y.address, 36 | // ); 37 | ``` 38 | 39 | source: https://github.com/facebook/flow/issues/5479#issuecomment-349749477 40 | 41 | Unfortunatelly, Flow cannot uncover this version (which can also explode): 42 | 43 | ```js 44 | { 45 | y.address && y.address.fullAddress && {y.address.fullAddress}; 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /til/2022-03-06-math-imul.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Math.imul() 3 | tags: ['javascript'] 4 | --- 5 | 6 | See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul 7 | 8 | ```js 9 | console.log(Math.imul(3, 4)); 10 | // expected output: 12 11 | 12 | console.log(Math.imul(-5, 12)); 13 | // expected output: -60 14 | 15 | console.log(Math.imul(0xffffffff, 5)); 16 | // expected output: -5 17 | 18 | console.log(Math.imul(0xfffffffe, 5)); 19 | // expected output: -10 20 | ``` 21 | 22 | > The reason imul exists is because it is faster in only one (so far) circumstance: AsmJS. AsmJS allows for JIT-optimizers to more easily implement internal integers in JavaScript. Multiplying two numbers stored internally as integers (which is only possible with AsmJS) with imul is the only potential circumstance where Math.imul may prove performant in current browsers. 23 | 24 | Also: [Why would I use Math.imul()?](https://stackoverflow.com/questions/21052816/why-would-i-use-math-imul) 25 | -------------------------------------------------------------------------------- /til/2022-02-15-using-dev-constant-in-rescript.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to use __DEV__ constant in ReScript 3 | tags: ['rescript'] 4 | --- 5 | 6 | Global constant `__DEV__` is not part of the ReScript language, but you can use it via `%external`. It is a common pattern that allows you to easily distinguish dev and prod environments. I started using it in my projects [as well](https://github.com/adeira/universe/tree/e769861df645885f9c646416a4855ce944a3839c/src/babel-preset-adeira#__dev__-expression). 7 | 8 | ```reason 9 | switch %external(__DEV__) { 10 | | Some(_) => Js.log("dev mode") 11 | | None => Js.log("production mode") 12 | } 13 | ``` 14 | 15 | The code above translates to something like this: 16 | 17 | ```js 18 | var match$1 = typeof __DEV__ === 'undefined' ? undefined : __DEV__; 19 | if (match$1 !== undefined) { 20 | console.log('dev mode'); 21 | } else { 22 | console.log('production mode'); 23 | } 24 | ``` 25 | 26 | Obviously, this code still needs some kind of transpilation (since `__DEV__` doesn't exist in JS either). 27 | -------------------------------------------------------------------------------- /til/2019-09-30-computing-average-in-a-constant-time.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Computing average in a constant time 3 | tags: ['javascript'] 4 | --- 5 | 6 | _Also known as average streaming._ 7 | 8 | Source: https://stackoverflow.com/a/22999488/3135248 9 | 10 | > The following function adds a number to an average. average is the current average, size is the current number of values in the average, and value is the number to add to the average: 11 | 12 | ``` 13 | double addToAverage(double average, int size, double value) 14 | { 15 | return (size * average + value) / (size + 1); 16 | } 17 | ``` 18 | 19 | > Likewise, the following function removes a number from the average: 20 | 21 | ``` 22 | double subtractFromAverage(double average, int size, double value) 23 | { 24 | // if (size == 1) return 0; // wrong but then adding a value "works" 25 | // if (size == 1) return NAN; // mathematically proper 26 | // assert(size > 1); // debug-mode check 27 | // if(size < 2) throw(...) // always check 28 | return (size * average - value) / (size - 1); 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /til/2020-07-21-strictequals-without-equals-operators.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: strictEquals(a, b) without using "===" 3 | tags: ['javascript'] 4 | --- 5 | 6 | > Write a function called `strictEquals(a, b)` that returns the same value as `a === b`. Your implementation must not use the `===` or `!==` operators. 7 | 8 | Solution: https://gist.github.com/gaearon/08a85a33e3d08f3f2ca25fb17bd9d638?ck_subscriber_id=920605104 9 | 10 | ```js 11 | function strictEquals(a, b) { 12 | if (Object.is(a, b)) { 13 | // Same value. 14 | // Is this NaN? 15 | if (Object.is(a, NaN)) { 16 | // We already know a and b are the same, so it's enough to check a. 17 | // Special case #1. 18 | return false; 19 | } else { 20 | // They are equal! 21 | return true; 22 | } 23 | } else { 24 | // Different value. 25 | // Are these 0 and -0? 26 | if ((Object.is(a, 0) && Object.is(b, -0)) || (Object.is(a, -0) && Object.is(b, 0))) { 27 | // Special case #2. 28 | return true; 29 | } else { 30 | // They are not equal! 31 | return false; 32 | } 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /til/2019-08-19-gitignore.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gitignore 3 | tags: ['git'] 4 | --- 5 | 6 | ```gitignore 7 | # Empty lines are being ignored. 8 | # Trailing spaces are ignored unless they are quoted with backslash ("\"). 9 | 10 | \#not_a_comment.xyz 11 | !\#not_a_comment.xyz 12 | 13 | # If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself. 14 | # Otherwise the pattern may also match at any level below the .gitignore level. 15 | 16 | directory_only/ 17 | directory_or_file 18 | 19 | # An asterisk "*" matches anything except a slash. 20 | # The character "?" matches any one character except "/". 21 | # The range notation, e.g. [a-zA-Z], can be used to match one of the characters in a range. 22 | 23 | # Two consecutive asterisks ("**") in patterns matched against full pathname may have special meaning: 24 | **/foo # match in all directories 25 | abc/** # matches all files inside directory "abc" 26 | a/**/b # zero or more directories ("a/b", "a/x/b", "a/x/y/b", ...) 27 | ``` 28 | 29 | https://git-scm.com/docs/gitignore 30 | -------------------------------------------------------------------------------- /til/2019-05-22-v8-built-in-functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: V8 Built-in functions 3 | tags: ['javascript'] 4 | --- 5 | 6 | - https://v8.dev/docs/builtin-functions 7 | - https://github.com/v8/v8/blob/master/src/runtime/runtime.h 8 | - https://v8.dev/docs/memory-leaks 9 | 10 | ```js 11 | function foo() { 12 | const x = { bar: 'bar' }; 13 | %DebugTrackRetainingPath(x); 14 | return () => { 15 | return x; 16 | }; 17 | } 18 | const closure = foo(); 19 | gc(); 20 | ``` 21 | 22 | vvv 23 | 24 | ```text 25 | 💃 universe [master] node --allow-natives-syntax --track-retaining-path --expose-gc src/test.js 26 | 27 | ################################################# 28 | Retaining path for 0x33a90e9bcb89: 29 | 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 31 | Distance from root 3: 0x33a90e9bcb89 32 | 33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | Distance from root 2: 0x33a90e9bcb51 35 | 36 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 37 | Distance from root 1: 0x33a90e9bcc09 38 | 39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | Root: (Isolate) 41 | ------------------------------------------------- 42 | ``` 43 | -------------------------------------------------------------------------------- /playwright/ui/landingPages.play.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line ft-flow/require-valid-file-annotation,import/no-extraneous-dependencies 2 | import { test, expect } from '@playwright/test'; 3 | 4 | test('/til', async ({ page }) => { 5 | await page.goto('/'); 6 | await expect(page).toHaveTitle(/^Today I Learned \| Martin Zlámal 🤓$/); 7 | }); 8 | 9 | test('/til/2022/04/03/list-of-all-packages-installed-using-homebrew', async ({ page }) => { 10 | await page.goto('/til/2022/04/03/list-of-all-packages-installed-using-homebrew'); 11 | await expect(page).toHaveTitle( 12 | /^List of all packages installed using Homebrew | Martin Zlámal 🤓$/, 13 | ); 14 | }); 15 | 16 | test('/til-articles', async ({ page }) => { 17 | await page.goto('/til-articles'); 18 | await expect(page).toHaveTitle(/^Articles \| Martin Zlámal 🤓$/); 19 | }); 20 | 21 | test('/til-articles/2021/09/30/fbt-deep-dive', async ({ page }) => { 22 | await page.goto('/til-articles/2021/09/30/fbt-deep-dive'); 23 | await expect(page).toHaveTitle(/^FBT deep dive | Martin Zlámal 🤓$/); 24 | }); 25 | 26 | test('/docs/flow', async ({ page }) => { 27 | await page.goto('/docs/flow'); 28 | await expect(page).toHaveTitle(/^Flow all-in \| Martin Zlámal 🤓$/); 29 | }); 30 | -------------------------------------------------------------------------------- /til/2020-02-10-css-selectors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS selectors 3 | tags: ['css'] 4 | --- 5 | 6 | https://gist.github.com/magicznyleszek/809a69dd05e1d5f12d01 7 | 8 | ```css 9 | /* Present: selects .foo elements with bar attribute present, regardless of its value */ 10 | .foo[bar] { 11 | fum: baz; 12 | } 13 | 14 | /* Exact: selects .foo elements where the bar attribute has the exact value of fum */ 15 | .foo[bar='fum'] { 16 | baz: qux; 17 | } 18 | 19 | /* Whitespace separated: selects .foo elements with bar attribute values contain specified partial value of fum (whitespace separated) */ 20 | .foo[bar~='fum'] { 21 | baz: qux; 22 | } 23 | 24 | /* Hyphen separated: selects .foo elements with bar attribute values contain specified partial value of fum immediately followed by hyphen (-) character */ 25 | .foo[bar|='fum'] { 26 | baz: qux; 27 | } 28 | 29 | /* Begins with: selects .foo elements where the bar attribute begins with fum */ 30 | .foo[bar^='fum'] { 31 | baz: qux; 32 | } 33 | 34 | /* Ends with: selects .foo elements where the bar attribute ends with fum */ 35 | .foo[bar$='fum'] { 36 | baz: qux; 37 | } 38 | 39 | /* Containts: selects .foo elements where the bar attribute contains string fum followed and preceded by any number of other characters */ 40 | .foo[bar*='fum'] { 41 | baz: qux; 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /sidebars.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * Copyright (c) 2017-present, Facebook, Inc. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 10 | module.exports = { 11 | meta: [ 12 | { 13 | type: 'category', 14 | label: 'Flow', 15 | items: [ 16 | { type: 'doc', id: 'flow' }, 17 | { type: 'doc', id: 'flow/saved-state' }, 18 | { type: 'doc', id: 'flow/unsealed-objects' }, 19 | { type: 'doc', id: 'flow/configuration' }, 20 | { type: 'doc', id: 'flow/debugging' }, 21 | { 22 | type: 'category', 23 | label: 'Patterns', 24 | items: ['flow/patterns/exhaustive-checking'], 25 | }, 26 | { type: 'doc', id: 'flow/shenanigans' }, 27 | ], 28 | }, 29 | { 30 | type: 'category', 31 | label: 'Relay', 32 | items: [ 33 | { type: 'doc', id: 'relay' }, 34 | { type: 'doc', id: 'relay/directives' }, 35 | { type: 'doc', id: 'relay/local-schema' }, 36 | { type: 'doc', id: 'relay/match-module' }, 37 | { type: 'doc', id: 'relay/uploadables' }, 38 | ], 39 | }, 40 | { type: 'doc', id: 'rust' }, 41 | { type: 'doc', id: 'git' }, 42 | { type: 'doc', id: 'graphql' }, 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /til/2019-09-26-understanding-git-diff.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Understanding `@@ -1,2 +3,4 @@` part of the Git diff' 3 | tags: ['git'] 4 | --- 5 | 6 | ```text 7 | diff -u <(seq 16) <(seq 16 | grep -Ev '^(2|3|14|15)$') 8 | --- /dev/fd/11 2019-09-26 15:48:31.000000000 -0500 9 | +++ /dev/fd/12 2019-09-26 15:48:31.000000000 -0500 10 | // highlight-next-line 11 | @@ -1,6 +1,4 @@ 12 | 1 13 | -2 14 | -3 15 | 4 16 | 5 17 | 6 18 | // highlight-next-line 19 | @@ -11,6 +9,4 @@ 20 | 11 21 | 12 22 | 13 23 | -14 24 | -15 25 | 16 26 | ``` 27 | 28 | - `-1,6` means that this piece of the first file starts at line 1 and shows a total of 6 lines; therefore it shows lines 1 to 6 (`-` means "old", as we usually invoke it as `diff -u old new`) 29 | - `+1,4` means that this piece of the second file starts at line 1 and shows a total of 4 lines; therefore it shows lines 1 to 4 (`+` means "new") 30 | - `@@ -11,6 +9,4 @@` for the second hunk is analogous: in the old file, we have 6 lines, starting at line 11 of the old file; in the new file, we have 4 lines, starting at line 9 of the new file 31 | 32 | > This is for anyone who still didn't quite understand. In `@@ -1,6 +1,4 @@` pls don't read `-1` as "minus one" or `+1` as "plus one". Instead read this as "line 1 to 6" in old (first) file. Note here `-` implies "old" _not minus_. 33 | 34 | Source: https://stackoverflow.com/a/31615438/3135248 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build and run Dockerfile: 2 | # 3 | # docker build -t test . 4 | # docker run --rm -p 3000:3000 -it test 5 | 6 | # Install dependencies only when needed 7 | FROM node:21.7.3-alpine AS deps 8 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 9 | RUN apk add --no-cache libc6-compat 10 | WORKDIR /app 11 | COPY package.json ./ 12 | RUN yarn install # --immutable 13 | 14 | # Rebuild the source code only when needed 15 | FROM node:21.7.3-alpine AS builder 16 | WORKDIR /app 17 | COPY . . 18 | COPY --from=deps /app/node_modules ./node_modules 19 | RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN \ 20 | SENTRY_AUTH_TOKEN=$(cat /run/secrets/SENTRY_AUTH_TOKEN) \ 21 | yarn build && yarn install # --immutable 22 | 23 | # Production image, copy all the files and run Docusaurus 24 | FROM node:21.7.3-alpine AS runner 25 | WORKDIR /app 26 | 27 | ENV NODE_ENV production 28 | 29 | COPY --from=builder /app/docusaurus.config.js ./ 30 | COPY --from=builder /app/sidebars.js ./ 31 | COPY --from=builder /app/build ./build 32 | COPY --from=builder /app/src/css ./src/css 33 | COPY --from=builder /app/node_modules ./node_modules 34 | COPY --from=builder /app/package.json ./package.json 35 | 36 | EXPOSE 3000 37 | 38 | CMD ["yarn", "docusaurus", "serve", "--host", "0.0.0.0", "--no-open"] 39 | -------------------------------------------------------------------------------- /src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | /** 9 | * Any CSS included here will be global. The classic template 10 | * bundles Infima by default. Infima is a CSS framework designed to 11 | * work well for content-centric websites. 12 | */ 13 | 14 | /* You can override the default Infima variables here. */ 15 | :root { 16 | --ifm-color-primary: #25c2a0; 17 | --ifm-color-primary-dark: rgb(33, 175, 144); 18 | --ifm-color-primary-darker: rgb(31, 165, 136); 19 | --ifm-color-primary-darkest: rgb(26, 136, 112); 20 | --ifm-color-primary-light: rgb(70, 203, 174); 21 | --ifm-color-primary-lighter: rgb(102, 212, 189); 22 | --ifm-color-primary-lightest: rgb(146, 224, 208); 23 | } 24 | 25 | .docusaurus-highlight-code-line { 26 | background-color: rgb(73, 81, 111); 27 | } 28 | 29 | /* 30 | h2 ~ h1, 31 | h3 ~ h1, 32 | h4 ~ h1, 33 | h5 ~ h1, 34 | h6 ~ h1, 35 | h3 ~ h2:first-of-type, 36 | h4 ~ h2:first-of-type, 37 | h5 ~ h2:first-of-type, 38 | h6 ~ h2:first-of-type, 39 | h4 ~ h3:first-of-type, 40 | h5 ~ h3:first-of-type, 41 | h6 ~ h3:first-of-type, 42 | h5 ~ h4:first-of-type, 43 | h6 ~ h4:first-of-type, 44 | h6 ~ h5:first-of-type { 45 | border: 2px dotted red !important; 46 | } 47 | */ 48 | -------------------------------------------------------------------------------- /til/2019-09-30-identify-css-layout-inconsistencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to easily identify inconsistencies in your CSS layout 3 | tags: ['css'] 4 | --- 5 | 6 | 7 | ```css 8 | * { outline: 1px solid rgba(255,0,0,.2); :hover { outline: 1px solid rgba(255,0,0,0.6); } } 9 | * * { outline: 1px solid rgba(0,255,0,.2); :hover { outline: 1px solid rgba(0,255,0,0.6); } } 10 | * * * { outline: 1px solid rgba(0,0,255,.2); :hover { outline: 1px solid rgba(0,0,255,0.6); } } 11 | * * * * { outline: 1px solid rgba(255,0,255,.2); :hover { outline: 1px solid rgba(255,0,0,0.6); } } 12 | * * * * * { outline: 1px solid rgba(0,255,255,.2); :hover { outline: 1px solid rgba(0,255,0,0.6); } } 13 | * * * * * * { outline: 1px solid rgba(255,255,0,.2); :hover { outline: 1px solid rgba(0,0,255,0.6); } } 14 | * * * * * * * { outline: 1px solid rgba(255,0,0,.2); :hover { outline: 1px solid rgba(255,0,0,0.6); } } 15 | * * * * * * * * { outline: 1px solid rgba(0,255,0,.2); :hover { outline: 1px solid rgba(0,255,0,0.6); } } 16 | * * * * * * * * * { outline: 1px solid rgba(0,0,255,.2); :hover { outline: 1px solid rgba(0,0,255,0.6); } } 17 | ``` 18 | 19 | 20 | > Different depth of nodes will use different colour allowing you to see the size of each element on the page, their margin and their padding. Now you can easily identify inconsistencies. 21 | 22 | Source: https://dev.to/gajus/my-favorite-css-hack-32g3 23 | -------------------------------------------------------------------------------- /til/2019-09-26-object-preventextensions-seal-freeze.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Difference between Object.preventExtensions/seal/freeze 3 | tags: ['javascript'] 4 | --- 5 | 6 | All tested with `node --use_strict` (to prevent silent errors). 7 | 8 | ```js 9 | const obj = { a: 1 }; 10 | Object.preventExtensions(obj); 11 | obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌ 12 | obj.a = -1; // ✅ 13 | delete obj.a; // ✅ 14 | 15 | Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: true, enumerable: true, configurable: true } } 16 | ``` 17 | 18 | ```js 19 | const obj = { a: 1 }; 20 | Object.seal(obj); 21 | obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌ 22 | obj.a = -1; // ✅ 23 | delete obj.a; // TypeError: Cannot delete property 'a' of # ❌ 24 | 25 | Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: true, enumerable: true, configurable: false } } 26 | ``` 27 | 28 | ```js 29 | const obj = { a: 1 }; 30 | Object.freeze(obj); 31 | obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌ 32 | obj.a = -1; // TypeError: Cannot assign to read only property 'a' of object '#' ❌ 33 | delete obj.a; // TypeError: Cannot delete property 'a' of # ❌ 34 | 35 | Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: false, enumerable: true, configurable: false } } 36 | 37 | // You can basically only read: 38 | console.log(obj.a); 39 | ``` 40 | 41 | Note: Flow tracks these flags on objects: `frozen`, `sealed` and `exact`. 42 | -------------------------------------------------------------------------------- /til/2019-01-28-is-this-thing-a-number.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Is this thing a number? 3 | tags: ['javascript'] 4 | --- 5 | 6 | TL;DR - do not use only `isNaN` for this and write a lot of tests. 7 | 8 | StackOverflow [implementation](https://stackoverflow.com/a/1830844/3135248) (so far ✅): 9 | 10 | ```js 11 | function isNumeric(n) { 12 | return !isNaN(parseFloat(n)) && isFinite(n); 13 | } 14 | ``` 15 | 16 | Facebook [implementation](https://github.com/facebook/fbjs/blob/cfd39964ba4b9ce351c314ed512e654ffa9cad26/packages/fbjs/src/useragent/VersionRange.js#L210-L218) (❌ fails for many values like `null`, booleans, Date object, empty strings, ...): 17 | 18 | ```js 19 | function isNumber(number) { 20 | return !isNaN(number) && isFinite(number); 21 | } 22 | ``` 23 | 24 | @cookielab implementation (❌ fails for values like `7.2acdgs` and it's not multiplatform): 25 | 26 | ```js 27 | function isNumeric(n) { 28 | return Number.isFinite(Number.parseFloat(n)); 29 | } 30 | ``` 31 | 32 | Please note that `isNaN` and `Number.isNaN` [differs significantly](https://stackoverflow.com/a/25176685/3135248) (`isNaN` performs a type conversion). The same for `isFinite` vs `Number.isFinite`: 33 | 34 | > In comparison to the global `isFinite()` function, this method doesn't forcibly convert the parameter to a number. This means only values of the type number, that are also finite, return `true`. 35 | 36 | See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite 37 | 38 | ```js 39 | Number.isFinite('0'); // false, would've been true with global isFinite('0') 40 | Number.isFinite(null); // false, would've been true with global isFinite(null) 41 | ``` 42 | 43 | Polyfill (to understand the difference better): 44 | 45 | ```js 46 | Number.isFinite = 47 | Number.isFinite || 48 | function (value) { 49 | return typeof value === 'number' && isFinite(value); 50 | }; 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/relay/match-module.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: match-module 3 | title: Match & Module 4 | sidebar_label: Match & Module 5 | --- 6 | 7 | > Incremental Delivery and Data-Driven Dependencies (3D) 8 | 9 | These directives allow you to lazily load union results. First, it requires `JSDependency` GraphQL scalar. Next it's necessary to use Union field with this shape: 10 | 11 | ```graphql 12 | union FormattedContent = PlainContent | BoldContent 13 | 14 | type Todo implements Node { 15 | id: ID! 16 | content(supported: [String!]!): FormattedContent 17 | complete: Boolean! 18 | } 19 | ``` 20 | 21 | Directive `@match` can be used on the field `content` only if it follows this shape. And lastly, directive `@module` can be used on `JSDependency` fields: 22 | 23 | ```graphql 24 | type BoldContent { 25 | data: BoldContentData 26 | js(module: String): JSDependency 27 | } 28 | ``` 29 | 30 | Usage: 31 | 32 | ```graphql 33 | fragment Todo_todo on Todo { 34 | complete 35 | id 36 | content @match { 37 | ...PlainTodoRenderer_value @module(name: "PlainTodoRenderer.react") 38 | ...BoldTodoRenderer_value @module(name: "BoldTodoRenderer.react") 39 | } 40 | } 41 | ``` 42 | 43 | ```js 44 | const PlainTodoRenderer = React.lazy(() => 45 | import(/* webpackChunkName: "PlainTodoRenderer" */ './PlainTodoRenderer'), 46 | ); 47 | const BoldTodoRenderer = React.lazy(() => 48 | import(/* webpackChunkName: "BoldTodoRenderer" */ './BoldTodoRenderer'), 49 | ); 50 | 51 | const MatchContainer = ({ match, fallback }: Props) => { 52 | switch (match.__module_component) { 53 | case 'BoldTodoRenderer.react': 54 | return ( 55 | 56 | 57 | 58 | ); 59 | case 'PlainTodoRenderer.react': 60 | return ( 61 | 62 | 63 | 64 | ); 65 | default: 66 | return 'Nothing matched...'; 67 | } 68 | }; 69 | ``` 70 | 71 | _still researching_ 72 | 73 | - https://github.com/relayjs/relay-examples/pull/95 74 | - https://github.com/relayjs/relay-examples/pull/96 75 | -------------------------------------------------------------------------------- /docs/flow/patterns/exhaustive-checking.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: exhaustive-checking 3 | title: Exhaustive checking with empty type 4 | sidebar_label: Exhaustive checking 5 | --- 6 | 7 | [flow.org/try](https://flow.org/try/#0C4TwDgpgBAwghgZwgqBeKByAghqAfTAIQwG4AoMgMwFcA7AY2AEsB7WqCADwAs5qFgACk4AuWImQBKKAG8yUKE0pRhaVOmwZpchQoD0eqABMWUAdUqV5UAL4cANkkXLV6jcW3X9hk8aWUIACcIWmAzYAsrBTsIR2gdXQMoNnsQKGBA6mglKAB3aF5aI3sIIyg4e3soegkELxVRDgBbMFBJcmiyGzIgA) 8 | 9 | ```js 10 | type Cases = 'A' | 'B'; 11 | 12 | function exhaust(x: Cases) { 13 | if (x === 'A') { 14 | // do stuff 15 | } else if (x === 'B') { 16 | // do different stuff 17 | } else { 18 | // highlight-start 19 | // only true if we handled all cases 20 | (x: empty); 21 | // highlight-end 22 | } 23 | } 24 | ``` 25 | 26 | What happens when you add new case `'C'`? You will get this error: 27 | 28 | ```text 29 | Cannot cast x to empty because string literal C [1] is incompatible with empty [2]. 30 | 31 | [1] 5│ function exhaust(x: Cases) { 32 | 6│ if (x === 'A') { 33 | 7│ // do stuff 34 | 8│ } else if (x === 'B') { 35 | 9│ // do different stuff 36 | 10│ } else { 37 | 11│ // only true if we handled all cases 38 | [2] 12│ (x: empty); 39 | 13│ } 40 | 14│ } 41 | 15│ 42 | ``` 43 | 44 | You can be sure that you covered all the cases this way. Another real-life example: 45 | 46 | ```js 47 | export function exhaustB(reason: 'magicLink' | 'signUpConfirmation' | 'resetPassword') { 48 | switch (reason) { 49 | case 'magicLink': 50 | return __('account.check_email_magic_link'); 51 | case 'signUpConfirmation': 52 | return __('account.check_email_sign_up'); 53 | case 'resetPassword': 54 | return __('account.you_will_recieve_password'); 55 | default: 56 | // highlight-next-line 57 | return invariant(false, 'Unsupported reason: %j', (reason: empty)); 58 | } 59 | } 60 | ``` 61 | 62 | Notice how is the `empty` type used at the same time with `reason`. 63 | 64 | - https://github.com/facebook/flow/commit/c603505583993aa953904005f91c350f4b65d6bd 65 | - https://medium.com/@ibosz/advance-flow-type-1-exhaustive-checking-with-empty-type-a02e503cd3a0 66 | - https://github.com/facebook/flow/pull/7655/files 67 | -------------------------------------------------------------------------------- /til/2019-09-26-placement-of-catch-before-and-after-then.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Placement of catch BEFORE and AFTER then 3 | tags: ['javascript'] 4 | --- 5 | 6 | Source: https://stackoverflow.com/questions/42013104/placement-of-catch-before-and-after-then 7 | 8 | ```js 9 | return p.then(...).catch(...); 10 | 11 | // - vs - 12 | 13 | return p.catch(...).then(...); 14 | ``` 15 | 16 | There are differences either when p resolves or rejects, but whether those differences matter or not depends upon what the code inside the `.then()` or `.catch()` handlers does. 17 | 18 | ### What happens when p resolves 19 | 20 | In the first scheme, when p resolves, the .then() handler is called. If that .then() handler either returns a value or another promise that eventually resolves, then the .catch() handler is skipped. But, if the .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler will execute for both a reject in the original promise p, but also an error that occurs in the .then() handler. 21 | 22 | In the second scheme, when p resolves, the .then() handler is called. If that .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler cannot catch that because it is before it in the chain. 23 | 24 | So, that's difference #1. If the .catch() handler is AFTER, then it can also catch errors inside the .then() handler. 25 | 26 | ### What happens when p rejects 27 | 28 | Now, in the first scheme, if the promise p rejects, then the .then() handler is skipped and the .catch() handler will be called as you would expect. What you do in the .catch() handler determines what is returned as the final result. If you just return a value from the .catch() handler or return a promise that eventually resolves, then the promise chain switches to the resolved state because you "handled" the error and returned normally. If you throw or return a rejected promise in the .catch() handler, then the returned promise stays rejected. 29 | 30 | In the second scheme, if the promise p rejects, then the .catch() handler is called. If you return a normal value or a promise that eventually resolves from the .catch() handler (thus "handling" the error), then the promise chain switches to the resolved state and the .then() handler after the .catch() will be called. 31 | 32 | So that's difference #2. If the .catch() handler is BEFORE, then it can handle the error and allow the .then() handler to still get called. 33 | -------------------------------------------------------------------------------- /docs/flow/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: configuration 3 | title: Configuration 4 | sidebar_label: Configuration 5 | --- 6 | 7 | Flow is a complex tool and it can be complicated to setup your project correctly. This page should help with more advanced things you cannot find in docs since they are mostly about experiences and know-how. 8 | 9 | ## `[ignore]` 10 | 11 | By default, Flow does check every folder in your project looking for JS/JSON files. Therefore, you should explicitly ignore Git directory: 12 | 13 | ```ini 14 | ; https://flow.org/en/docs/config/ignore/ 15 | [ignore] 16 | /.git/.* 17 | ``` 18 | 19 | You could expect that there are not JS files in this directory. But what happens what you name your branch `package.json` for example? Git represents branch names via files and therefore you get similar error: 20 | 21 | ```text 22 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ .git/logs/refs/heads/package.json:1:1 23 | 24 | Expected an object literal 25 | 26 | 1│ 0000000000000000000000000000000000000000 19b4f8985ebb633cdf37afd18705625d53a3883e Martin Zlámal 1569868294 -0500 branch: Created from HEAD 27 | 2│ 28 | 29 | 30 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ .git/refs/heads/package.json:1:1 31 | 32 | Unexpected token ILLEGAL 33 | 34 | 1│ 19b4f8985ebb633cdf37afd18705625d53a3883e 35 | 2│ 36 | 37 | 38 | 39 | Found 2 errors 40 | ``` 41 | 42 | Source: https://github.com/facebook/flow/issues/5148 43 | 44 | ## `[rollouts]` config 45 | 46 | The optional rollout section has 0 or more lines. Each line defines a single rollout. For example: 47 | 48 | ```ini 49 | [rollouts] 50 | 51 | testA=40% on, 60% off 52 | testB=50% blue, 20% yellow, 30% pink 53 | ``` 54 | 55 | The first line defines a rollout named "testA" with two groups. The second line defines a rollout named "testB" with three groups. Each rollout's groups must sum to 100. Some config examples (usages): 56 | 57 | ```ini 58 | [rollouts] 59 | verify_sig=0% on, 100% off 60 | 61 | [options] 62 | (verify_sig=on) experimental.well_formed_exports=true 63 | ``` 64 | 65 | See: https://github.com/facebook/flow/pull/8018/files 66 | 67 | ## Common configuration issues 68 | 69 | 1. Accidentally disabled flow for ALL JavaScript files 70 | 71 | ```ini 72 | module.file_ext=.json 73 | # do not forget to define '.js' here as well otherwise you basically disabled Flow 74 | ``` 75 | 76 | See: https://flow.org/en/docs/config/options/#toc-module-file-ext-string 77 | 78 | 2. Inncorrectly used `resolve_dirname` instead of `name_mapper` 79 | 80 | See: https://github.com/facebook/flow/pull/5850 81 | -------------------------------------------------------------------------------- /docs/relay/uploadables.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: uploadables 3 | title: Relay Uploadables 4 | sidebar_label: Uploadables 5 | keywords: 6 | - relay 7 | - graphql 8 | - file upload 9 | - uploadables 10 | - multipart 11 | --- 12 | 13 | Sending normal GraphQL mutation is trivial: 14 | 15 | ```js 16 | commitMutation(graphql` 17 | mutation TestMutation { 18 | test(input: "test") 19 | } 20 | `); 21 | ``` 22 | 23 | It sends POST request with these headers: 24 | 25 | ```http 26 | Accept: application/json 27 | Content-type: application/json 28 | User-Agent: Mozilla/5.0 29 | … 30 | ``` 31 | 32 | And with this request payload: 33 | 34 | ```json 35 | { 36 | "query": "mutation TestMutation {\n test(input: \"test\")\n}\n", 37 | "variables": {} 38 | } 39 | ``` 40 | 41 | We can use the same POST request to send our files as well. To do so, you have to use uploadables from Relay and `multipart/form-data` content type. Mutation is similar: 42 | 43 | ```js 44 | commitMutation( 45 | graphql` 46 | mutation TestMutation { 47 | test(input: "test") 48 | } 49 | `, 50 | { 51 | uploadables: { 52 | file1: new File(['foo'], 'foo.txt', { 53 | type: 'text/plain', 54 | }), 55 | file2: new File(['bar'], 'bar.txt', { 56 | type: 'text/plain', 57 | }), 58 | }, 59 | }, 60 | ); 61 | ``` 62 | 63 | With these headers: 64 | 65 | ```http 66 | Accept: */* 67 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryshXxygBlT4ATOyhW 68 | User-Agent: Mozilla/5.0 69 | … 70 | ``` 71 | 72 | And with these form data: 73 | 74 | ``` 75 | ------WebKitFormBoundaryshXxygBlT4ATOyhW 76 | Content-Disposition: form-data; name="query" 77 | 78 | mutation TestMutation { 79 | test(input: "test") 80 | } 81 | 82 | ------WebKitFormBoundaryshXxygBlT4ATOyhW 83 | Content-Disposition: form-data; name="variables" 84 | 85 | {} 86 | ------WebKitFormBoundaryshXxygBlT4ATOyhW 87 | Content-Disposition: form-data; name="file1"; filename="foo.txt" 88 | Content-Type: text/plain 89 | 90 | foo 91 | ------WebKitFormBoundaryshXxygBlT4ATOyhW 92 | Content-Disposition: form-data; name="file2"; filename="bar.txt" 93 | Content-Type: text/plain 94 | 95 | bar 96 | ------WebKitFormBoundaryshXxygBlT4ATOyhW-- 97 | ``` 98 | 99 | Please note - it's a good idea to create GraphQL type representing the file and send it in the query as well. Look at how [Absinthe is doing it](https://hexdocs.pm/absinthe/file-uploads.html): 100 | 101 | ``` 102 | $ curl -X POST \\ 103 | -F query="{mutation uploadFile(users: \"users_csv\", metadata: \"metadata_json\")" \\ 104 | -F users_csv=@users.csv \\ 105 | -F metadata_json=@metadata.json \\ 106 | localhost:4000/graphql 107 | ``` 108 | 109 | They send the actual files and query as `multipart/form-data` as well but they require the file name (with underscore) in the query and it will fail if these two things do not match. Simple and elegant. 110 | 111 | > By treating uploads as regular arguments we get all the usual GraphQL argument benefits (such as validation and documentation)---which we wouldn't get if we were merely putting them in the context as in other implementations. 112 | 113 | - https://github.com/facebook/relay/issues/1844#issuecomment-316893590 114 | - https://hexdocs.pm/absinthe/file-uploads.html 115 | -------------------------------------------------------------------------------- /til/2022-03-12-turbofish.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Bastion of the Turbofish 3 | tags: ['rust'] 4 | --- 5 | 6 | The Turbofish remains undefeated. 7 | 8 | See: https://github.com/rust-lang/rust/blob/f103b2969b0088953873dc1ac92eb3387c753596/src/test/ui/parser/bastion-of-the-turbofish.rs 9 | 10 | ```rust 11 | // Beware travellers, lest you venture into waters callous and unforgiving, 12 | // where hope must be abandoned, ere it is cruelly torn from you. For here 13 | // stands the bastion of the Turbofish: an impenetrable fortress holding 14 | // unshaking against those who would dare suggest the supererogation of the 15 | // Turbofish. 16 | // 17 | // Once I was young and foolish and had the impudence to imagine that I could 18 | // shake free from the coils by which that creature had us tightly bound. I 19 | // dared to suggest that there was a better way: a brighter future, in which 20 | // Rustaceans both new and old could be rid of that vile beast. But alas! In 21 | // my foolhardiness my ignorance was unveiled and my dreams were dashed 22 | // unforgivingly against the rock of syntactic ambiguity. 23 | // 24 | // This humble program, small and insignificant though it might seem, 25 | // demonstrates that to which we had previously cast a blind eye: an ambiguity 26 | // in permitting generic arguments to be provided without the consent of the 27 | // Great Turbofish. Should you be so naïve as to try to revolt against its 28 | // mighty clutches, here shall its wrath be indomitably displayed. This 29 | // program must pass for all eternity: forever watched by the guardian angel 30 | // which gave this beast its name, and stands fundamentally at odds with the 31 | // impetuous rebellion against the Turbofish. 32 | // 33 | // My heart aches in sorrow, for I know I am defeated. Let this be a warning 34 | // to all those who come after: for they too must overcome the impassible 35 | // hurdle of defeating the great beast, championed by a resolute winged 36 | // guardian. 37 | // 38 | // Here stands the Bastion of the Turbofish, a memorial to Anna Harren, 39 | // Guardian Angel of these Hallowed Grounds. <3 40 | 41 | // See https://github.com/rust-lang/rust/pull/53562 42 | // and https://github.com/rust-lang/rfcs/pull/2527 43 | // and https://twitter.com/garblefart/status/1393236602856611843 44 | // for context. 45 | 46 | fn main() { 47 | let (the, guardian, stands, resolute) = ("the", "Turbofish", "remains", "undefeated"); 48 | let _: (bool, bool) = (the(resolute)); 49 | } 50 | ``` 51 | 52 | Still unclear why is this code problematic? This explains the issue better: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=415ae154471981ffe05b372c85b1e031 53 | 54 | ```rust 55 | fn main() { 56 | let (oh, woe, is, me) = ("oh", "woe", "is", "me"); 57 | 58 | // at first glance this looks like "oh" is a function 59 | // which is being called with 2 type args: "woe" & "is" 60 | // and being invoked with 1 arg: "me" 61 | // which obvs doesn't make any sense since we know these are all &str 62 | let _ = (oh(me)); 63 | 64 | // after staring at it for a bit the outer parens aren't superfluous 65 | // but define a tuple, with the comma in the middle separate 2 tuple items. 66 | 67 | // the left tuple item is the result of comparing "oh" to "woe" which 68 | // results in a bool, and the same is true for the right tuple item, 69 | // although "me" is wrapped in superfluous parens just to be confusing. 70 | 71 | // here's it but more clearly formatted 72 | let _: (bool, bool) = (oh < woe, is > me); 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/flow/unsealed-objects.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: unsealed-objects 3 | title: Unsealed objects 4 | sidebar_label: Unsealed objects 5 | --- 6 | 7 | Tl;dr: they can be tricky. 8 | 9 | - docs: https://flow.org/en/docs/types/objects/#toc-unsealed-objects 10 | - issues to study: https://github.com/facebook/flow/labels/unsealed%20objects 11 | 12 | Unsealed objects are a special case in Flow. They allow you to create an empty object to be able to add properties later (see the docs). There are some special properties worth mentioning: 13 | 14 | 1. reading unknown property from unsealed objects is unsafe 15 | 16 | ```js 17 | const obj = {}; 18 | 19 | obj.foo = 1; 20 | obj.bar = true; 21 | 22 | const foo: number = obj.foo; // Works! 23 | const bar: boolean = obj.bar; // Works! 24 | const baz: string = obj.baz; // Works? (reads from unsealed objects with no matching writes are never checked) 25 | 26 | const fail: string = obj.foo; // Errors correctly. 27 | ``` 28 | 29 | 2. you cannot assign unsealed object to the exact type 30 | 31 | ```js 32 | type Foo = {| a?: string, b?: string |}; 33 | 34 | const foo1: Foo = { a: '' }; // works as expected 35 | const foo2: Foo = {}; // doesn't work, but should (?) 36 | const foo3: Foo = { ...null }; // this is equivalent to {} but is not an unsealed object (it's sealed) 37 | const foo4: Foo = Object.freeze({}); // alternatively, seal the object manually 38 | ``` 39 | 40 | - https://github.com/facebook/flow/issues/7566#issuecomment-526534111 41 | - https://github.com/facebook/flow/issues/2977#issuecomment-390827317 42 | 43 | ## Gotchas 44 | 45 | ```js 46 | const a: { foo: string } = {}; // No error?! ❌ 47 | const b: { foo: string } = { ...null }; // Error ✅ 48 | 49 | const x: {} = ''; // Error ✅ 50 | ``` 51 | 52 | ```js 53 | type Record = { 54 | foo: number, 55 | bar: string, 56 | }; 57 | 58 | const x: Record = {}; // No error?! ❌ 59 | ``` 60 | 61 | https://github.com/facebook/flow/issues/8091 62 | 63 | There is also a know bug with unsealed objects vs. optional chaining lint: 64 | 65 | ```js 66 | // @flow strict 67 | 68 | type State = {| 69 | base?: {| 70 | data: {| 71 | a: string, 72 | |}, 73 | |}, 74 | |}; 75 | 76 | const getBase = (state: State) => state.base ?? {}; 77 | 78 | // Fixed version: 79 | // const getBase = (state: State) => state.base ?? { data: undefined }; 80 | 81 | export const getA = (state: State) => getBase(state).data?.a; 82 | ``` 83 | 84 | This core results incorrectly in: 85 | 86 | ```text 87 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/optional-chain-flow.js:16:39 88 | 89 | This use of optional chaining (?.) is unnecessary because getBase(...).data [1] cannot be nullish or because an earlier 90 | ?. will short-circuit the nullish case. (unnecessary-optional-chain) 91 | 92 | 13│ // Fixed version: 93 | 14│ // const getBase = (state: State) => state.base ?? { data: undefined }; 94 | 15│ 95 | [1] 16│ export const getA = (state: State) => getBase(state).data?.a; 96 | 17│ 97 | 98 | Found 1 error 99 | ``` 100 | 101 | See: https://github.com/facebook/flow/issues/8065 102 | 103 | ## Note on `Object` type 104 | 105 | This is most probably not really about sealed types but it feels somehow related. 106 | 107 | This is fine: 108 | 109 | ```js 110 | const test: { a: string } = {}; 111 | const x = test.a; // ✅ 112 | test.a = '2'; // ✅ 113 | ``` 114 | 115 | But you cannot read/write from an empty `Object` type 116 | 117 | ```js 118 | const test: {} = {}; 119 | const x = test.a; // ❌ 120 | test.a = 2; // ❌ 121 | ``` 122 | 123 | Previously, `Object` type was internally defined like `{ [key: string]: any }` which implies what you can do with it: 124 | 125 | ```js 126 | const test: { [key: string]: any } = {}; 127 | const x = test.a; // ✅ 128 | test.a = '2'; // ✅ 129 | ``` 130 | 131 | ```js 132 | const test: { [key: string]: any } = {}; 133 | const x = test.a; // ✅ 134 | test.a = 2; // ✅ 135 | ``` 136 | -------------------------------------------------------------------------------- /docs/flow/shenanigans.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: shenanigans 3 | title: Flow Shenanigans 4 | sidebar_label: Shenanigans 5 | --- 6 | 7 | :::note 8 | Not every example here is a bug. Sometimes it's just unexpected behavior resulting from the type-system design or from type system theory in general. 9 | ::: 10 | 11 | - https://gist.github.com/kangax/aa59598cf28d02f38579d8a95b5cbf92 12 | 13 | ## Dangerous array access 14 | 15 | None of the typing systems can handle this correctly, all show no error during static analysis (but could be runtime error). 16 | 17 | Flow ([pr](https://github.com/facebook/flow/pull/6823)): 18 | 19 | ```js 20 | let a = [1, 2, 3]; 21 | let b: number = a[10]; // undefined 22 | let c = b * 2; 23 | ``` 24 | 25 | Typescript ([issue](https://github.com/Microsoft/TypeScript/issues/13778)): 26 | 27 | ```js 28 | let a = [1, 2, 3]; 29 | let b: number = a[10]; // undefined 30 | let c = b * 2; 31 | ``` 32 | 33 | ReasonML: 34 | 35 | ```re 36 | let a: array(int) = [|1, 2, 3|]; 37 | let b: int = a[10] // undefined 38 | let c = b * 2 39 | ``` 40 | 41 | ## `boolean` is incompatible with `true | false` 42 | 43 | ```js 44 | declare function foo(true | false): void; 45 | declare function bar(): boolean; 46 | 47 | foo(bar()); 48 | ``` 49 | 50 | ``` 51 | 4: foo(bar()) 52 | ^ Cannot call `foo` with `bar()` bound to the first parameter because: Either boolean [1] is incompatible with boolean literal `true` [2]. Or boolean [1] is incompatible with boolean literal `false` [3]. 53 | References: 54 | 2: declare function bar(): boolean ^ [1] 55 | 1: declare function foo(true | false): void 56 | ^ [2] 57 | 1: declare function foo(true | false): void 58 | ^ [3] 59 | ``` 60 | 61 | https://github.com/facebook/flow/issues/4196 62 | 63 | ## `mixed` type cannot be exhausted 64 | 65 | ```js 66 | declare var flowDebugPrint: $Flow$DebugPrint; 67 | 68 | function test(x: mixed) { 69 | if (typeof x === 'string') { 70 | return true; 71 | } 72 | flowDebugPrint(x); // still 'mixed', see the output 🤔 73 | } 74 | ``` 75 | 76 | [flow.org/try](https://flow.org/try/#0CYUwxgNghgTiAEA3W8BmED2B3AIiARgK4DmACjAJYB2ALgFzwAkAYplo3kWZbQNwBQ-VISpgaFDFXg0QAZxoAKAB4MAthSUhgASngBvfvHgVU8BTQCeABxAZTS+AF5n8AOTyexV7oNGjcGkIYKRoYQhABIwBfQzQ2ThJyakUlbV54AHoM+HkKCAg3dU1gVwAaHJAEGgALBAxCGisG+EA+DcAUXf4YoA) 77 | 78 | Debug output: 79 | 80 | ```json 81 | { 82 | "reason": { 83 | "pos": { 84 | "source": "-", 85 | "type": "SourceFile", 86 | "start": { "line": 7, "column": 18 }, 87 | "end": { "line": 7, "column": 18 } 88 | }, 89 | "desc": "mixed" 90 | }, 91 | "kind": "MixedT" 92 | } 93 | ``` 94 | 95 | One solution is to manually define your custom mixed type which [can be exhausted](https://flow.org/try/#0PTAEAEDMBsHsHcBQiAuBPADgU1AWTbgJYAeWAJqALyiKigA+oAbrIRbQ6AHYCu00NOo14BbAEZYAToM4BnFJMJcA5jMZjYsaFgCGXNaADeAOlMBfAwEFJknWgA8+IqTIA+AwApTxnZOWyALjwCEnIAbQBdAEoqdydQsg46GgBjWC55UBxqDx0g+UUVABpmIPiXGMp3QzNkLA8AcgaSgEYoxHqmktz8hSVlErEg0QlJSuqzKKA). 96 | 97 | ## Incorrect array destructing 98 | 99 | ```js 100 | const [w, a, t] = { p: '' }; // no error 101 | ``` 102 | 103 | [flow.org/try](https://flow.org/try/#0MYewdgzgLgBA2gdwDQwIYqgXRgXhgbwAcAuGAcjIF8BuIA) 104 | 105 | On the other hand, TS is OK with this code (while Flow throws an error correctly): 106 | 107 | ```js 108 | const foo: {} = ''; 109 | ``` 110 | 111 | https://www.typescriptlang.org/play/index.html#code/MYewdgzgLgBAZiEAuGBvAvjAvDA5LgbiA 112 | 113 | ## Defaults for non-existent properties are allowed 114 | 115 | ```js 116 | const React = require('react'); 117 | 118 | function Component({ defaultProp = 'string' }) { 119 | return null; 120 | } 121 | 122 | ; 123 | ``` 124 | 125 | This is allowed even though some people would expect something like "Error, defaultProp is missing in props", it's a feature: https://github.com/facebook/flow/commit/6dec7d5dbbd12a6f210f7c3ae21841a932eb71a8 (from 0.109.0) 126 | -------------------------------------------------------------------------------- /docs/flow/debugging.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: debugging 3 | title: Debugging 4 | sidebar_label: Debugging 5 | --- 6 | 7 | ## `$Flow$DebugPrint`, `$Flow$DebugThrow`, `$Flow$DebugSleep` 8 | 9 | Throw and sleep are not very useful in normal code. Throw kills Flow and sleep adds delay in seconds. More useful is debug print which prints debug information: 10 | 11 | ```js 12 | // @flow strict 13 | const x = 10; 14 | declare var flowDebugPrint: $Flow$DebugPrint; 15 | flowDebugPrint(x); 16 | ``` 17 | 18 | Output: 19 | 20 | ```text 21 | 💃 universe [master] y flow 22 | yarn run v1.16.0 23 | $ /Users/mrtnzlml/Work/kiwi-private/incubator/universe/node_modules/.bin/flow 24 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/test.js:4:1 25 | 26 | { 27 | "reason":{ 28 | "pos":{ 29 | "source":"/Users/mrtnzlml/Work/kiwi-private/incubator/universe/src/test.js", 30 | 31 | "type":"SourceFile", 32 | "start":{"line":4,"column":16}, 33 | "end":{"line":4,"column":16} 34 | }, 35 | 36 | "desc":"number" 37 | }, 38 | "kind":"NumT", 39 | "literal":"10" 40 | } 41 | 42 | 1│ // @flow strict 43 | 2│ const x = 10; 44 | 3│ declare var flowDebugPrint: $Flow$DebugPrint; 45 | 4│ flowDebugPrint(x); 46 | 5│ 47 | 48 | 49 | 50 | Found 1 error 51 | ``` 52 | 53 | ## Advanced debugging 54 | 55 | `yarn flow` errors may be sometimes very cryptic: 56 | 57 | ``` 58 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/apps/autobooking/queries/Autobooking.js:27:26 59 | 60 | Cannot call await with context.dataLoaders.autobooking.getResult(...) bound to p because property autobooking is missing 61 | in Promise [1]. 62 | 63 | src/apps/autobooking/queries/Autobooking.js 64 | 24│ { bid }: argsType, 65 | 25│ context: GraphqlContextType 66 | 26│ ): Promise => { 67 | 27│ const result = await context.dataLoaders.autobooking.getResult(bid) 68 | 28│ const { autobooking, status } = result 69 | 29│ 70 | 30│ if (autobooking === null) { 71 | 72 | src/apps/autobooking/Datasource.js 73 | [1] 19│ ): Promise<{| 74 | 20│ +autobooking: Autobooking | null, 75 | 21│ +status: string 76 | 22│ |}> { 77 | ``` 78 | 79 | It helps to inspect the whole stacktrace using `yarn flow --show-all-branches`: 80 | 81 | ``` 82 | Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/apps/autobooking/queries/Autobooking.js:27:26 83 | 84 | Cannot call await with context.dataLoaders.autobooking.getResult(...) bound to p because: 85 | • Either cannot return object literal because in property status of type argument R [1]: 86 | • Either string [2] is incompatible with string literal pending [3]. 87 | • Or string [2] is incompatible with string literal check_failed [4]. 88 | • Or string [2] is incompatible with string literal ready [5]. 89 | • Or string [2] is incompatible with string literal started [6]. 90 | • Or string [2] is incompatible with string literal too_expensive [7]. 91 | • Or property autobooking is missing in Promise [8]. 92 | 93 | src/apps/autobooking/queries/Autobooking.js 94 | 24│ { bid }: argsType, 95 | 25│ context: GraphqlContextType 96 | 26│ ): Promise => { 97 | 27│ const result = await context.dataLoaders.autobooking.getResult(bid) 98 | 28│ const { autobooking, status } = result 99 | 29│ 100 | 30│ if (autobooking === null) { 101 | 102 | /private/tmp/flow/flowlib_2c621631/core.js 103 | [1] 583│ declare class Promise<+R> { 104 | 105 | src/apps/autobooking/Datasource.js 106 | [8] 19│ ): Promise<{| 107 | 20│ +autobooking: Autobooking | null, 108 | [2] 21│ +status: string 109 | 22│ |}> { 110 | 111 | src/apps/autobooking/apiTypes/Autobooking.js 112 | [3][4][5][6][7] 7│ status: 'pending' | 'check_failed' | 'ready' | 'started' | 'too_expensive' 113 | ``` 114 | 115 | You can eventually use `yarn flow check --traces 100` 116 | -------------------------------------------------------------------------------- /til-articles/2019-10-14-flow-react-restricted-element.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Flow: React RestrictedElement' 3 | --- 4 | 5 | `RestrictedElement` 6 | 7 | ```js 8 | export type RestrictedElement<+TElementType: React$ElementType> = {| 9 | +type: 10 | | TElementType 11 | | ClassComponentRender> 12 | | FunctionComponentRender>, 13 | +props: any, // The props type is already captured in the type field, 14 | // and using ElementProps recursively can get very expensive. Instead 15 | // of paying for that computation, I decided to use any 16 | 17 | +key: React$Key | null, 18 | +ref: any, 19 | |}; 20 | ``` 21 | 22 | [flow.org/try](https://flow.org/try/#0PTAEAEDMBsHsHcBQjgCpQFMDO0CWA7AFwFoATXLAQwCNoNRVhkBjWfLQ0AJQ0uc4C8oAE4YAjgFdcogBQByUX0JyAlAG5khAJ4AHegDEJ+frjYBhWAFsdbDER75SGYQB4A1ABUHT4QD5QQjI6wrA6WABcoJT4WioB-l52PhqI2nqgZtCUWFgW1rb2Sc7uiY7O-kKZ2VguiKDcvPwAJHk2+HaELtFaADRRMf4AZKAEhM6QfPQA3iJFwjIqkaU+oAC+PYi+KRgAHjbCnGn0PBzCuPwYpACidJYdJTcYd0QeuhiRPEpNj8+Er3oVUBTAA+dVAbiO4TB9WBoA8Pw6-ww0NAsKqOVaBUI3mKJ0IZwu11u93hxJeb18vhRsMMxkIpnwmPahTKrjxBLGRKeJIR5IBvg29TcwVCEX6vVAIDhAAt6CKwqAjiMsFFoIpSFpQMxKDpCBJRKQRvhFbLFW9QJBcBhoKRBZKwNFDRIsAQAOagXmEAAKIQVomY+pdADdrZrtcbXRhOCHhJrdnp2LgQwA6UAASXYY0opDBUtgkFAOkoWjdFtgwhNlE4rGsEkIVYZfTToCczFwTkNhFgoGd9G6yCFAGsMFoPo1CE0ANIj1GgfASaDQO1uUSQSLdDbA1YpKUATVgEi10Uw+Eg5eY9BrNhd9LYlGgharY2E7FA8FwhGlDVO505nqhzBZDkoAALJ2BIaZjJYmA7GMjgqp8-DJky9wglu-hTKsiCAdUoHgQAyhgRbCFW5YwXBpAIeOyFWG0qHAuhQJYThwFgfO5FJFRSg0fkzKdCCYLMNKuA2qI+BjtxZjCaJdgAILCCRWi1PU9Tsr+lyei4Rz5nh86QU8-iwmphKadpBZsRIhHEaRfh2gKiCMZh2FASqABysCELJFkoUQHHwQ0km0ViLhoasGFYSgYCyYuJqXtJpBiWaegqvecDwJcybYWwHCKtghAAIwBKAMhgi4FlUipoBleB+nQcAFUqdV85WZQJFdhW9WlcA5WIOoyBSu5nneUFfHKnOHn9KqaWXFq8WZawma5RwABMRUlfUTUSA1G0WbVkrbVVFktW1ZGdZVUxoKATT6GlVx7BgJkKeWkSDV54E+ZwFDjZwqUIDNBCxbMP6ErNIkJXYmBkoQWAogwRq6YeV5YqmADy+DQJqu1Qf0hpHURrU2VEohTX9pCpoAoOQMMAWGVS4r3DbxHT7V1PV9c5uE4sIWBvXp2O7BRXFIR9IUMWFQJgmJPgLBJSGmW8OlYwZ4uVaIeovodNXY-VGj1FhEULTlYwcAAzGtpU9bTit1Qdm3HYTZ2NZz3NW8zG3deBVJsyxVGslg+FWFGwn4K6HjSlWTs8xBfOwZxAVCyN9GObmYD7oeiiLpqWDSgeNqgAAVs6nC9t++LqVyvygLKoh9NQdbpm+0SEMnirdk4ljZfiVb0J+9AwLsuDUCJH5aJl9SS840tx4QyZy3oOkR1bGEoqr+rGi4C+a08+062siARVKIHlt3YfGoVdAhg+ThEVli1G4QAAsZs7R7KKbXtDvP81+MnR1NtO-7dxPxulDuHOYztN7WxZi-NmUoABCuBXSRhyglSg8BqB8EHMVXA7c4hfU-FWUAslqCnCUB9ZU+A5DVlog2Wg9B3yfkiiaL6RZCDPnwKmZs8BG4t1mtESMlZCAAEJipBlwJQSurCwjhBAK6D80oJDUGTDWYAedqAhHgPgZa+UACsKhEBAA), alternative to: https://flow.org/en/docs/react/children/#toc-only-allowing-a-specific-element-type-as-children 23 | 24 | The one recommended in the docs is just for children with exactly that type. `RestrictedElement` lets you also use things that _render_ things of that type. So suppose you have some Button class. Let's say the button has a disabled flag. You might want to make another class: 25 | 26 | ```js 27 | class DisabledButton { 28 | render(): React.Element { ... } 29 | } 30 | ``` 31 | 32 | You can't use `DisabledButton` inside a children array for `React.Element` but you can in `RestrictedElement`. (via [@jbrown215](https://github.com/jbrown215)) 33 | 34 | Real example: 35 | 36 | ```js 37 | // @flow 38 | 39 | import * as React from 'react'; 40 | 41 | import { type RestrictedElement } from './RestrictedElement'; 42 | 43 | class Button extends React.Component<{| +disabled?: boolean |}> { 44 | render() { 45 | return null; 46 | } 47 | } 48 | 49 | class DisabledButton extends React.Component<{||}> { 50 | // The return type is not necessary - it's here only to demonstrate what is going on. 51 | render(): React.Element { 52 | return