├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci-check.yaml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── experimental └── experimental.txt ├── jest.config.json ├── package-lock.json ├── package.json ├── src ├── __snapshots__ │ ├── aliases-and-guards.spec.ts.snap │ ├── functional-helpers.spec.ts.snap │ ├── mapped-types.spec.ts.snap │ └── utility-types.spec.ts.snap ├── aliases-and-guards.snap.ts ├── aliases-and-guards.spec.snap.ts ├── aliases-and-guards.spec.ts ├── aliases-and-guards.ts ├── functional-helpers.spec.snap.ts ├── functional-helpers.spec.ts ├── functional-helpers.ts ├── index.ts ├── mapped-types.spec.snap.ts ├── mapped-types.spec.ts ├── mapped-types.ts ├── utility-types.spec.snap.ts ├── utility-types.spec.ts └── utility-types.ts ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── utils └── test-utils.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | 12 | # Indentation for md files 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | 17 | # Indentation for all source files 18 | [*.{json,js,jsx,ts,tsx,css,html}] 19 | trim_trailing_whitespace = true 20 | indent_style = space 21 | indent_size = 2 22 | insert_final_newline = true 23 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at piotrek.witek@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: piotrekwitek # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: piotrwitek # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://www.buymeacoffee.com/piotrekwitek"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | ## Steps to Reproduce 10 | 17 | 18 | ## Expected behavior 19 | 20 | 21 | ## Suggested solution(s) 22 | 23 | 24 | ## Project Dependencies 25 | - Utility-Types Version: X.X.X 26 | - TypeScript Version: X.X.X 27 | - tsconfig.json: 28 | 29 | 30 | ## Environment (optional) 31 | 32 | - Browser and Version: XXX 33 | - OS: XXX 34 | - Node Version: XXX 35 | - Package Manager and Version: XXX 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Have a question? Please check our spectrum community chat. 4 | --- 5 | 6 | First of all please check our spectrum community chat and we recommend to ask your question there for a quickest response and the indexing in search engines: 7 | - https://spectrum.chat/utility-types 8 | 9 | The only good reason to use issue tracker for your questions would be for "special requests" that doesn't fit into bug reports and feature requests categories. 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | ## Is your feature request related to a real problem or use-case? 7 | 8 | 9 | ## Describe a solution including usage in code example 10 | 11 | 12 | ## Who does this impact? Who is this for? 13 | 14 | 15 | ## Describe alternatives you've considered (optional) 16 | 17 | 18 | ## Additional context (optional) 19 | 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Description 5 | 6 | 7 | ## Related issues: 8 | - Resolved #XXX 9 | 10 | ## Checklist 11 | 12 | * [ ] I have read [CONTRIBUTING.md](https://github.com/piotrwitek/utility-types/blob/master/CONTRIBUTING.md) 13 | * [ ] I have linked all related issues above 14 | * [ ] I have rebased my branch 15 | 16 | For bugfixes: 17 | * [ ] I have added at least one unit test to confirm the bug have been fixed 18 | * [ ] I have checked and updated TOC and API Docs when necessary 19 | 20 | For new features: 21 | * [ ] I have added entry in TOC and API Docs 22 | * [ ] I have added a short example in API Docs to demonstrate new usage 23 | * [ ] I have added type unit tests with `dts-jest` 24 | -------------------------------------------------------------------------------- /.github/workflows/ci-check.yaml: -------------------------------------------------------------------------------- 1 | name: CI Check 2 | on: 3 | pull_request: 4 | types: [opened, synchronize] 5 | 6 | jobs: 7 | build: 8 | name: Build and Test 9 | timeout-minutes: 5 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 18 19 | cache: 'npm' 20 | 21 | - name: CI Check Script 22 | run: npm run ci-check 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # node-waf configuration 18 | .lock-wscript 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 25 | node_modules 26 | 27 | # Custom 28 | .alm/ 29 | .vscode/ 30 | .idea/ 31 | .tmp/ 32 | .DS_Store 33 | .env 34 | *.pyc 35 | 36 | # Build 37 | dist/ 38 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/* 3 | !LICENSE 4 | !README.md 5 | !CHANGELOG.md 6 | !SECURITY.md 7 | !SUPPORT.md 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | All changelogs can be found in [Releases](https://github.com/piotrwitek/utility-types/releases) 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | 1. Make sure you have read and understand the **Motivation** section to be aligned with the project goals. 4 | 2. Before submitting a PR please make a comment in the relevant issue to inform maintainers that you would like to work on it. 5 | 3. If this is something new and there is no issue asociated with the PR you would want to create it first using either "Feature Request" or "Bug Report" template. 6 | 4. All workflow scripts (like prettier, linter, tests) have to pass successfully. They are run on a CI server by `npm run ci-check` script. 7 | 5. Code coverage should not decline both in terms of type and runtime unit tests. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Piotr Witek (http://piotrwitek.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # utility-types 4 | 5 | Collection of utility types, complementing TypeScript built-in mapped types and aliases (think "lodash" for static types). 6 | 7 | [![Latest Stable Version](https://img.shields.io/npm/v/utility-types.svg)](https://www.npmjs.com/package/utility-types) 8 | [![NPM Downloads](https://img.shields.io/npm/dm/utility-types.svg)](https://www.npmjs.com/package/utility-types) 9 | [![NPM Downloads](https://img.shields.io/npm/dt/utility-types.svg)](https://www.npmjs.com/package/utility-types) 10 | [![Bundlephobia Size](https://img.shields.io/bundlephobia/minzip/utility-types.svg)](https://www.npmjs.com/package/utility-types) 11 | 12 | [![CI Check](https://github.com/piotrwitek/utility-types/actions/workflows/ci-check.yaml/badge.svg)](https://github.com/piotrwitek/utility-types/actions/workflows/ci-check.yaml) 13 | [![License](https://img.shields.io/npm/l/utility-types.svg?style=flat)](https://david-dm.org/piotrwitek/utility-types?type=peer) 14 | [![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/utility-types) 15 | 16 | _Found it useful? Want more updates?_ 17 | 18 | [**Show your support by giving a :star:**](https://github.com/piotrwitek/utility-types/stargazers) 19 | 20 | 21 | Buy Me a Coffee 22 | 23 | 24 | Become a Patron 25 | 26 | 27 |

28 | 29 | ### **What's new?** 30 | 31 | :tada: _Added new utilities_ :tada: 32 | 33 |

34 | 35 |
36 | 37 | ## Features 38 | 39 | * Providing a set of [Common Types](#table-of-contents) for TypeScript projects that are idiomatic and complementary to existing [TypeScript Mapped Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html) so you don't need to copy them between the projects. 40 | * Providing a set of [Additional Types](#) compatible with [Flow's Utility Types](https://flow.org/en/docs/types/utilities/) to allow much easier migration to `TypeScript`. 41 | 42 | ## Goals 43 | 44 | * Quality - thoroughly tested for type correctness with type-testing library `dts-jest` 45 | * Secure and minimal - no third-party dependencies 46 | * No runtime cost - it's type-level only 47 | 48 | ## Installation 49 | 50 | ```bash 51 | # NPM 52 | npm install utility-types 53 | 54 | # YARN 55 | yarn add utility-types 56 | ``` 57 | 58 | ## Compatibility Notes 59 | 60 | **TypeScript support** 61 | * `v3.x.x` - TypeScript v3.1+ 62 | * `v2.x.x` - TypeScript v2.8.1+ 63 | * `v1.x.x` - TypeScript v2.7.2+ 64 | 65 | ## Funding Issues 66 | **Utility-Types** is an open-source project created by people investing their time for the benefit of our community. 67 | 68 | Issues like bug fixes or feature requests can be very quickly resolved when funded through the IssueHunt platform. 69 | 70 | I highly recommend adding a bounty to the issue that you're waiting for to attract some contributors willing to work on it. 71 | 72 | [![Let's fund issues in this repository](https://issuehunt.io/static/embed/issuehunt-button-v1.svg)](https://issuehunt.io/repos/76400842) 73 | 74 | ## Contributing 75 | 76 | We are open for contributions. If you're planning to contribute please make sure to read the contributing guide as it can save you from wasting your time: [CONTRIBUTING.md](/CONTRIBUTING.md) 77 | 78 | --- 79 | 80 | * _(built-in)_ - types built-in TypeScript, no need to import 81 | 82 | # Table of Contents 83 | 84 | ## Aliases & Type Guards 85 | 86 | * [`Primitive`](#primitive) 87 | * [`isPrimitive`](#isprimitive) 88 | * [`Falsy`](#falsy) 89 | * [`isFalsy`](#isfalsy) 90 | * [`Nullish`](#nullish) 91 | * [`isNullish`](#isnullish) 92 | 93 | ## Union operators 94 | 95 | * [`SetIntersection`](#setintersectiona-b-same-as-extract) 96 | * [`SetDifference`](#setdifferencea-b-same-as-exclude) 97 | * [`SetComplement`](#setcomplementa-a1) 98 | * [`SymmetricDifference`](#symmetricdifferencea-b) 99 | * [`Exclude`](#excludea-b) _(built-in)_ 100 | * [`Extract`](#extracta-b) _(built-in)_ 101 | * [`NonNullable`](#nonnullablea) _(built-in)_ 102 | * [`NonUndefined`](#nonundefineda) 103 | 104 | ## Object operators 105 | 106 | * [`FunctionKeys`](#functionkeyst) 107 | * [`NonFunctionKeys`](#nonfunctionkeyst) 108 | * [`MutableKeys`](#mutablekeyst) 109 | * [`ReadonlyKeys`](#readonlykeyst) 110 | * [`RequiredKeys`](#requiredkeyst) 111 | * [`OptionalKeys`](#optionalkeyst) 112 | * [`UnionKeys`](#unionkeysu) 113 | * [`Optional`](#optionalt-k) 114 | * [`Partial`](#partialt) _(built-in)_ 115 | * [`DeepPartial`](#deeppartialt) 116 | * [`Required`](#requiredt-k) 117 | * [`DeepRequired`](#deeprequiredt) 118 | * [`Readonly`](#readonlyt) _(built-in)_ 119 | * [`DeepReadonly`](#deepreadonlyt) 120 | * [`Mutable`](#mutablet) 121 | * [`Pick` _(built-in)_](#pickt-k-built-in) 122 | * [`Omit`](#omitt-k) _(built-in)_ 123 | * [`PickByValue`](#pickbyvaluet-valuetype) 124 | * [`PickByValueExact`](#pickbyvalueexactt-valuetype) 125 | * [`OmitByValue`](#omitbyvaluet-valuetype) 126 | * [`OmitByValueExact`](#omitbyvalueexactt-valuetype) 127 | * [`Intersection`](#intersectiont-u) 128 | * [`Diff`](#difft-u) 129 | * [`Subtract`](#subtractt-t1) 130 | * [`Overwrite`](#overwritet-u) 131 | * [`Assign`](#assignt-u) 132 | * [`ValuesType`](#valuestypet) 133 | 134 | ## Special operators 135 | 136 | * [`ReturnType`](#returntypet) _(built-in)_ 137 | * [`InstanceType`](#instancetypet) _(built-in)_ 138 | * [`PromiseType`](#promisetypet) 139 | * [`Unionize`](#unionizet) 140 | * [`Brand`](#brandt-u) 141 | * [`UnionToIntersection`](#uniontointersectionu) 142 | 143 | ## Flow's Utility Types 144 | 145 | * [`$Keys`](#keyst) 146 | * [`$Values`](#valuest) 147 | * [`$ReadOnly`](#readonly2) 148 | * [`$Diff`](#diff2) 149 | * [`$PropertyType`](#propertytypet-k) 150 | * [`$ElementType`](#elementtypet-k) 151 | * [`$Call`](#callt) 152 | * [`$Shape`](#shapet) 153 | * [`$NonMaybeType`](#nonmaybetypet) 154 | * [`Class`](#classt) 155 | * [`mixed`](#mixed) 156 | 157 | ## Deprecated API (use at own risk) 158 | * `getReturnOfExpression()` - from TS v2.0 it's better to use type-level `ReturnType` instead 159 | 160 | --- 161 | 162 | ### `Primitive` 163 | 164 | Type representing primitive types in JavaScript, and thus TypeScript: `string | number | bigint | boolean | symbol | null | undefined` 165 | 166 | You can test for singular of these types with [`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) 167 | 168 | ### `isPrimitive` 169 | 170 | This is a [TypeScript Typeguard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) for the [`Primitive`](#primitive) type. 171 | 172 | This can be useful to control the type of a parameter as the program flows. Example: 173 | 174 | ```ts 175 | const consumer = (param: Primitive[] | Primitive): string => { 176 | if (isPrimitive(param)) { 177 | // typeof param === Primitive 178 | return String(param) + ' was Primitive'; 179 | } 180 | // typeof param === Primitive[] 181 | const resultArray = param 182 | .map(consumer) 183 | .map(rootString => '\n\t' + rootString); 184 | return resultArray.reduce((comm, newV) => comm + newV, 'this was nested:'); 185 | }; 186 | ``` 187 | 188 | [⇧ back to top](#table-of-contents) 189 | 190 | ### `Falsy` 191 | 192 | Type representing falsy values in TypeScript: `false | "" | 0 | null | undefined` 193 | > Except `NaN` which cannot be represented as a type literal 194 | 195 | ### `isFalsy` 196 | 197 | ```ts 198 | const consumer = (param: Falsy | string): string => { 199 | if (isFalsy(param)) { 200 | // typeof param === Falsy 201 | return String(param) + ' was Falsy'; 202 | } 203 | // typeof param === string 204 | return param.toString(); 205 | }; 206 | ``` 207 | 208 | [⇧ back to top](#table-of-contents) 209 | 210 | ### `Nullish` 211 | 212 | Type representing [nullish values](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing) in TypeScript: `null | undefined` 213 | 214 | [⇧ back to top](#table-of-contents) 215 | 216 | ### `isNullish` 217 | 218 | ```ts 219 | const consumer = (param: Nullish | string): string => { 220 | if (isNullish(param)) { 221 | // typeof param === Nullish 222 | return String(param) + ' was Nullish'; 223 | } 224 | // typeof param === string 225 | return param.toString(); 226 | }; 227 | ``` 228 | 229 | [⇧ back to top](#table-of-contents) 230 | 231 | ### `SetIntersection` (same as Extract) 232 | 233 | Set intersection of given union types `A` and `B` 234 | 235 | **Usage:** 236 | 237 | ```ts 238 | import { SetIntersection } from 'utility-types'; 239 | 240 | // Expect: "2" | "3" 241 | type ResultSet = SetIntersection<'1' | '2' | '3', '2' | '3' | '4'>; 242 | // Expect: () => void 243 | type ResultSetMixed = SetIntersection void), Function>; 244 | ``` 245 | 246 | [⇧ back to top](#table-of-contents) 247 | 248 | ### `SetDifference` (same as Exclude) 249 | 250 | Set difference of given union types `A` and `B` 251 | 252 | **Usage:** 253 | 254 | ```ts 255 | import { SetDifference } from 'utility-types'; 256 | 257 | // Expect: "1" 258 | type ResultSet = SetDifference<'1' | '2' | '3', '2' | '3' | '4'>; 259 | // Expect: string | number 260 | type ResultSetMixed = SetDifference void), Function>; 261 | ``` 262 | 263 | [⇧ back to top](#table-of-contents) 264 | 265 | ### `SetComplement` 266 | 267 | Set complement of given union types `A` and (it's subset) `A1` 268 | 269 | **Usage:** 270 | 271 | ```ts 272 | import { SetComplement } from 'utility-types'; 273 | 274 | // Expect: "1" 275 | type ResultSet = SetComplement<'1' | '2' | '3', '2' | '3'>; 276 | ``` 277 | 278 | [⇧ back to top](#table-of-contents) 279 | 280 | ### `SymmetricDifference` 281 | 282 | Set difference of union and intersection of given union types `A` and `B` 283 | 284 | **Usage:** 285 | 286 | ```ts 287 | import { SymmetricDifference } from 'utility-types'; 288 | 289 | // Expect: "1" | "4" 290 | type ResultSet = SymmetricDifference<'1' | '2' | '3', '2' | '3' | '4'>; 291 | ``` 292 | 293 | [⇧ back to top](#table-of-contents) 294 | 295 | ### `NonNullable` 296 | 297 | Exclude `null` and `undefined` from set `A` 298 | 299 | [⇧ back to top](#table-of-contents) 300 | 301 | ### `NonUndefined` 302 | 303 | Exclude `undefined` from set `A` 304 | 305 | [⇧ back to top](#table-of-contents) 306 | 307 | ### `Exclude` 308 | 309 | Exclude subset `B` from set `A` 310 | 311 | [⇧ back to top](#table-of-contents) 312 | 313 | ### `Extract` 314 | 315 | Extract subset `B` from set `A` 316 | 317 | [⇧ back to top](#table-of-contents) 318 | 319 | ## Operations on objects 320 | 321 | ### `FunctionKeys` 322 | 323 | Get union type of keys that are functions in object type `T` 324 | 325 | **Usage:** 326 | 327 | ```ts 328 | import { FunctionKeys } from 'utility-types'; 329 | 330 | type MixedProps = { name: string; setName: (name: string) => void }; 331 | 332 | // Expect: "setName" 333 | type Keys = FunctionKeys; 334 | ``` 335 | 336 | [⇧ back to top](#table-of-contents) 337 | 338 | ### `NonFunctionKeys` 339 | 340 | Get union type of keys that are non-functions in object type `T` 341 | 342 | **Usage:** 343 | 344 | ```ts 345 | import { NonFunctionKeys } from 'utility-types'; 346 | 347 | type MixedProps = { name: string; setName: (name: string) => void }; 348 | 349 | // Expect: "name" 350 | type Keys = NonFunctionKeys; 351 | ``` 352 | 353 | [⇧ back to top](#table-of-contents) 354 | 355 | ### `MutableKeys` 356 | 357 | Get union type of keys that are mutable (not readonly) in object type `T` 358 | 359 | Alias: `WritableKeys` 360 | 361 | **Usage:** 362 | 363 | ```ts 364 | import { MutableKeys } from 'utility-types'; 365 | 366 | type Props = { readonly foo: string; bar: number }; 367 | 368 | // Expect: "bar" 369 | type Keys = MutableKeys; 370 | ``` 371 | 372 | [⇧ back to top](#table-of-contents) 373 | 374 | ### `ReadonlyKeys` 375 | 376 | Get union type of keys that are readonly in object type `T` 377 | 378 | **Usage:** 379 | 380 | ```ts 381 | import { ReadonlyKeys } from 'utility-types'; 382 | 383 | type Props = { readonly foo: string; bar: number }; 384 | 385 | // Expect: "foo" 386 | type Keys = ReadonlyKeys; 387 | ``` 388 | 389 | [⇧ back to top](#table-of-contents) 390 | 391 | ### `RequiredKeys` 392 | 393 | Get union type of keys that are required in object type `T` 394 | 395 | **Usage:** 396 | 397 | ```ts 398 | import { RequiredKeys } from 'utility-types'; 399 | 400 | type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; 401 | 402 | // Expect: "req" | "reqUndef" 403 | type Keys = RequiredKeys; 404 | ``` 405 | 406 | [⇧ back to top](#table-of-contents) 407 | 408 | ### `OptionalKeys` 409 | 410 | Get union type of keys that are optional in object type `T` 411 | 412 | **Usage:** 413 | 414 | ```ts 415 | import { OptionalKeys } from 'utility-types'; 416 | 417 | type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; 418 | 419 | // Expect: "opt" | "optUndef" 420 | type Keys = OptionalKeys; 421 | ``` 422 | 423 | [⇧ back to top](#table-of-contents) 424 | 425 | ### `UnionKeys` 426 | 427 | Get keys of all objects in the union type `U` 428 | 429 | **Usage:** 430 | 431 | ```ts 432 | import { UnionKeys } from 'utility-types'; 433 | 434 | type Props = { name: string } | { age: number } | { visible: boolean }; 435 | 436 | // Expect: "name" | "age" | "visible" 437 | type Keys = UnionKeys; 438 | ``` 439 | 440 | [⇧ back to top](#table-of-contents) 441 | 442 | ### `Optional` 443 | 444 | From `T` make a set of properties by key `K` become optional 445 | 446 | **Usage:** 447 | 448 | ```ts 449 | import { Optional } from 'utility-types'; 450 | 451 | type Props = { name: string; age: number; visible: boolean; }; 452 | 453 | // Expect: { name?: string; age?: number; visible?: boolean; } 454 | type Props = Optional 455 | // Expect: { name: string; age?: number; visible?: boolean; } 456 | type Props = Optional; 457 | ``` 458 | 459 | [⇧ back to top](#table-of-contents) 460 | 461 | 462 | ### `Pick` _(built-in)_ 463 | 464 | From `T` pick a set of properties by key `K` 465 | 466 | **Usage:** 467 | 468 | ```ts 469 | type Props = { name: string; age: number; visible: boolean }; 470 | 471 | // Expect: { age: number; } 472 | type Props = Pick; 473 | ``` 474 | 475 | [⇧ back to top](#table-of-contents) 476 | 477 | ### `PickByValue` 478 | 479 | From `T` pick a set of properties by value matching `ValueType`. 480 | _(Credit: [Piotr Lewandowski](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c))_ 481 | 482 | **Usage:** 483 | 484 | ```ts 485 | import { PickByValue } from 'utility-types'; 486 | 487 | type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 488 | 489 | // Expect: { req: number } 490 | type Props = PickByValue; 491 | // Expect: { req: number; reqUndef: number | undefined; } 492 | type Props = PickByValue; 493 | ``` 494 | 495 | [⇧ back to top](#table-of-contents) 496 | 497 | ### `PickByValueExact` 498 | 499 | From `T` pick a set of properties by value matching exact `ValueType`. 500 | 501 | **Usage:** 502 | 503 | ```ts 504 | import { PickByValueExact } from 'utility-types'; 505 | 506 | type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 507 | 508 | // Expect: { req: number } 509 | type Props = PickByValueExact; 510 | // Expect: { reqUndef: number | undefined; } 511 | type Props = PickByValueExact; 512 | ``` 513 | 514 | [⇧ back to top](#table-of-contents) 515 | 516 | ### `Omit` 517 | 518 | From `T` remove a set of properties by key `K` 519 | 520 | **Usage:** 521 | 522 | ```ts 523 | import { Omit } from 'utility-types'; 524 | 525 | type Props = { name: string; age: number; visible: boolean }; 526 | 527 | // Expect: { name: string; visible: boolean; } 528 | type Props = Omit; 529 | ``` 530 | 531 | [⇧ back to top](#table-of-contents) 532 | 533 | ### `OmitByValue` 534 | 535 | From `T` remove a set of properties by value matching `ValueType`. 536 | _(Credit: [Piotr Lewandowski](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c))_ 537 | 538 | **Usage:** 539 | 540 | ```ts 541 | import { OmitByValue } from 'utility-types'; 542 | 543 | type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 544 | 545 | // Expect: { reqUndef: number | undefined; opt?: string; } 546 | type Props = OmitByValue; 547 | // Expect: { opt?: string; } 548 | type Props = OmitByValue; 549 | ``` 550 | 551 | [⇧ back to top](#table-of-contents) 552 | 553 | ### `OmitByValueExact` 554 | 555 | From `T` remove a set of properties by value matching exact `ValueType`. 556 | 557 | **Usage:** 558 | 559 | ```ts 560 | import { OmitByValueExact } from 'utility-types'; 561 | 562 | type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 563 | 564 | // Expect: { reqUndef: number | undefined; opt?: string; } 565 | type Props = OmitByValueExact; 566 | // Expect: { req: number; opt?: string } 567 | type Props = OmitByValueExact; 568 | ``` 569 | 570 | [⇧ back to top](#table-of-contents) 571 | 572 | ### `Intersection` 573 | 574 | From `T` pick properties that exist in `U` 575 | 576 | **Usage:** 577 | 578 | ```ts 579 | import { Intersection } from 'utility-types'; 580 | 581 | type Props = { name: string; age: number; visible: boolean }; 582 | type DefaultProps = { age: number }; 583 | 584 | // Expect: { age: number; } 585 | type DuplicatedProps = Intersection; 586 | ``` 587 | 588 | [⇧ back to top](#table-of-contents) 589 | 590 | ### `Diff` 591 | 592 | From `T` remove properties that exist in `U` 593 | 594 | **Usage:** 595 | 596 | ```ts 597 | import { Diff } from 'utility-types'; 598 | 599 | type Props = { name: string; age: number; visible: boolean }; 600 | type DefaultProps = { age: number }; 601 | 602 | // Expect: { name: string; visible: boolean; } 603 | type RequiredProps = Diff; 604 | ``` 605 | 606 | [⇧ back to top](#table-of-contents) 607 | 608 | ### `Subtract` 609 | 610 | From `T` remove properties that exist in `T1` (`T1` has a subset of the properties of `T`) 611 | 612 | **Usage:** 613 | 614 | ```ts 615 | import { Subtract } from 'utility-types'; 616 | 617 | type Props = { name: string; age: number; visible: boolean }; 618 | type DefaultProps = { age: number }; 619 | 620 | // Expect: { name: string; visible: boolean; } 621 | type RequiredProps = Subtract; 622 | ``` 623 | 624 | [⇧ back to top](#table-of-contents) 625 | 626 | ### `Overwrite` 627 | 628 | From `U` overwrite properties to `T` 629 | 630 | **Usage:** 631 | 632 | ```ts 633 | import { Overwrite } from 'utility-types'; 634 | 635 | type Props = { name: string; age: number; visible: boolean }; 636 | type NewProps = { age: string; other: string }; 637 | 638 | // Expect: { name: string; age: string; visible: boolean; } 639 | type ReplacedProps = Overwrite; 640 | ``` 641 | 642 | [⇧ back to top](#table-of-contents) 643 | 644 | ### `Assign` 645 | 646 | From `U` assign properties to `T` (just like object assign) 647 | 648 | **Usage:** 649 | 650 | ```ts 651 | import { Assign } from 'utility-types'; 652 | 653 | type Props = { name: string; age: number; visible: boolean }; 654 | type NewProps = { age: string; other: string }; 655 | 656 | // Expect: { name: string; age: string; visible: boolean; other: string; } 657 | type ExtendedProps = Assign; 658 | ``` 659 | 660 | [⇧ back to top](#table-of-contents) 661 | 662 | ### `ValuesType` 663 | 664 | Get the union type of all the values in an object, tuple, array or array-like type `T`. 665 | 666 | **Usage:** 667 | 668 | ```ts 669 | import { ValuesType } from 'utility-types'; 670 | 671 | type Props = { name: string; age: number; visible: boolean }; 672 | // Expect: string | number | boolean 673 | type PropsValues = ValuesType; 674 | 675 | type NumberArray = number[]; 676 | // Expect: number 677 | type NumberItems = ValuesType; 678 | 679 | type ReadonlyNumberTuple = readonly [1, 2]; 680 | // Expect: 1 | 2 681 | type AnotherNumberUnion = ValuesType; 682 | 683 | type BinaryArray = Uint8Array; 684 | // Expect: number 685 | type BinaryItems = ValuesType; 686 | ``` 687 | 688 | [⇧ back to top](#table-of-contents) 689 | 690 | ### `Partial` 691 | 692 | Make all properties of object type optional 693 | 694 | [⇧ back to top](#table-of-contents) 695 | 696 | ### `Required` 697 | 698 | From `T` make a set of properties by key `K` become required 699 | 700 | **Usage:** 701 | 702 | ```ts 703 | import { Required } from 'utility-types'; 704 | 705 | type Props = { name?: string; age?: number; visible?: boolean; }; 706 | 707 | // Expect: { name: string; age: number; visible: boolean; } 708 | type Props = Required 709 | // Expect: { name?: string; age: number; visible: boolean; } 710 | type Props = Required; 711 | ``` 712 | 713 | [⇧ back to top](#table-of-contents) 714 | 715 | ### `Readonly` 716 | 717 | Make all properties of object type readonly 718 | 719 | [⇧ back to top](#table-of-contents) 720 | 721 | ### `Mutable` 722 | 723 | From `T` make all properties become mutable 724 | 725 | Alias: `Writable` 726 | 727 | ```ts 728 | import { Mutable } from 'utility-types'; 729 | 730 | type Props = { 731 | readonly name: string; 732 | readonly age: number; 733 | readonly visible: boolean; 734 | }; 735 | 736 | // Expect: { name: string; age: number; visible: boolean; } 737 | Mutable; 738 | ``` 739 | 740 | [⇧ back to top](#table-of-contents) 741 | 742 | ### `ReturnType` 743 | 744 | Obtain the return type of a function 745 | 746 | [⇧ back to top](#table-of-contents) 747 | 748 | ### `InstanceType` 749 | 750 | Obtain the instance type of a class 751 | 752 | [⇧ back to top](#table-of-contents) 753 | 754 | ### `Unionize` 755 | 756 | Disjoin object to form union of objects, each with single property 757 | 758 | **Usage:** 759 | 760 | ```ts 761 | import { Unionize } from 'utility-types'; 762 | 763 | type Props = { name: string; age: number; visible: boolean }; 764 | 765 | // Expect: { name: string; } | { age: number; } | { visible: boolean; } 766 | type UnionizedType = Unionize; 767 | ``` 768 | 769 | [⇧ back to top](#table-of-contents) 770 | 771 | ### `PromiseType` 772 | 773 | Obtain Promise resolve type 774 | 775 | **Usage:** 776 | 777 | ```ts 778 | import { PromiseType } from 'utility-types'; 779 | 780 | // Expect: string 781 | type Response = PromiseType>; 782 | ``` 783 | 784 | [⇧ back to top](#table-of-contents) 785 | 786 | ### `DeepReadonly` 787 | 788 | Readonly that works for deeply nested structures 789 | 790 | **Usage:** 791 | 792 | ```ts 793 | import { DeepReadonly } from 'utility-types'; 794 | 795 | type NestedProps = { 796 | first: { 797 | second: { 798 | name: string; 799 | }; 800 | }; 801 | }; 802 | 803 | // Expect: { 804 | // readonly first: { 805 | // readonly second: { 806 | // readonly name: string; 807 | // }; 808 | // }; 809 | // } 810 | type ReadonlyNestedProps = DeepReadonly; 811 | ``` 812 | 813 | [⇧ back to top](#table-of-contents) 814 | 815 | ### `DeepRequired` 816 | 817 | Required that works for deeply nested structures 818 | 819 | **Usage:** 820 | 821 | ```ts 822 | import { DeepRequired } from 'utility-types'; 823 | 824 | type NestedProps = { 825 | first?: { 826 | second?: { 827 | name?: string; 828 | }; 829 | }; 830 | }; 831 | 832 | // Expect: { 833 | // first: { 834 | // second: { 835 | // name: string; 836 | // }; 837 | // }; 838 | // } 839 | type RequiredNestedProps = DeepRequired; 840 | ``` 841 | 842 | [⇧ back to top](#table-of-contents) 843 | 844 | ### `DeepNonNullable` 845 | 846 | NonNullable that works for deeply nested structure 847 | 848 | **Usage:** 849 | 850 | ```ts 851 | import { DeepNonNullable } from 'utility-types'; 852 | 853 | type NestedProps = { 854 | first?: null | { 855 | second?: null | { 856 | name?: string | null | undefined; 857 | }; 858 | }; 859 | }; 860 | 861 | // Expect: { 862 | // first: { 863 | // second: { 864 | // name: string; 865 | // }; 866 | // }; 867 | // } 868 | type RequiredNestedProps = DeepNonNullable; 869 | ``` 870 | 871 | [⇧ back to top](#table-of-contents) 872 | 873 | ### `DeepPartial` 874 | 875 | Partial that works for deeply nested structures 876 | 877 | **Usage:** 878 | 879 | ```ts 880 | import { DeepPartial } from 'utility-types'; 881 | 882 | type NestedProps = { 883 | first: { 884 | second: { 885 | name: string; 886 | }; 887 | }; 888 | }; 889 | 890 | // Expect: { 891 | // first?: { 892 | // second?: { 893 | // name?: string; 894 | // }; 895 | // }; 896 | // } 897 | type PartialNestedProps = DeepPartial; 898 | ``` 899 | 900 | [⇧ back to top](#table-of-contents) 901 | 902 | ### `Brand` 903 | 904 | Define nominal type of `U` based on type of `T`. Similar to Opaque types in Flow. 905 | 906 | **Usage:** 907 | 908 | ```ts 909 | import { Brand } from 'utility-types'; 910 | 911 | type USD = Brand 912 | type EUR = Brand 913 | 914 | const tax = 5 as USD; 915 | const usd = 10 as USD; 916 | const eur = 10 as EUR; 917 | 918 | function gross(net: USD): USD { 919 | return (net + tax) as USD; 920 | } 921 | 922 | gross(usd); // ok 923 | gross(eur); // Type '"EUR"' is not assignable to type '"USD"'. 924 | ``` 925 | 926 | [⇧ back to top](#table-of-contents) 927 | 928 | ### `UnionToIntersection` 929 | 930 | Get intersection type given union type `U` 931 | 932 | **Usage:** 933 | 934 | ```ts 935 | import { UnionToIntersection } from 'utility-types'; 936 | 937 | // Expect: { name: string } & { age: number } & { visible: boolean } 938 | UnionToIntersection<{ name: string } | { age: number } | { visible: boolean }> 939 | ``` 940 | 941 | [⇧ back to top](#table-of-contents) 942 | 943 | --- 944 | 945 | ## Flow's Utility Types 946 | 947 | ### `$Keys` 948 | 949 | get the union type of all the keys in an object type `T`
950 | https://flow.org/en/docs/types/utilities/#toc-keys 951 | 952 | **Usage:** 953 | 954 | ```ts 955 | import { $Keys } from 'utility-types'; 956 | 957 | type Props = { name: string; age: number; visible: boolean }; 958 | 959 | // Expect: "name" | "age" | "visible" 960 | type PropsKeys = $Keys; 961 | ``` 962 | 963 | [⇧ back to top](#flows-utility-types) 964 | 965 | ### `$Values` 966 | 967 | get the union type of all the values in an object type `T`
968 | https://flow.org/en/docs/types/utilities/#toc-values 969 | 970 | **Usage:** 971 | 972 | ```ts 973 | import { $Values } from 'utility-types'; 974 | 975 | type Props = { name: string; age: number; visible: boolean }; 976 | 977 | // Expect: string | number | boolean 978 | type PropsValues = $Values; 979 | ``` 980 | 981 | [⇧ back to top](#flows-utility-types) 982 | 983 | ###
`$ReadOnly` 984 | 985 | get the read-only version of a given object type `T`
986 | https://flow.org/en/docs/types/utilities/#toc-readonly 987 | 988 | **Usage:** 989 | 990 | ```ts 991 | import { $ReadOnly } from 'utility-types'; 992 | 993 | type Props = { name: string; age: number; visible: boolean }; 994 | 995 | // Expect: Readonly<{ name: string; age: number; visible: boolean; }> 996 | type ReadOnlyProps = $ReadOnly; 997 | ``` 998 | 999 | [⇧ back to top](#flows-utility-types) 1000 | 1001 | ### `$Diff` 1002 | 1003 | get the set difference of a given object types `T` and `U` (`T \ U`)
1004 | https://flow.org/en/docs/types/utilities/#toc-diff 1005 | 1006 | **Usage:** 1007 | 1008 | ```ts 1009 | import { $Diff } from 'utility-types'; 1010 | 1011 | type Props = { name: string; age: number; visible: boolean }; 1012 | type DefaultProps = { age: number }; 1013 | 1014 | // Expect: { name: string; visible: boolean; } 1015 | type RequiredProps = $Diff; 1016 | ``` 1017 | 1018 | [⇧ back to top](#flows-utility-types) 1019 | 1020 | ### `$PropertyType` 1021 | 1022 | get the type of property of an object at a given key `K`
1023 | https://flow.org/en/docs/types/utilities/#toc-propertytype 1024 | 1025 | **Usage:** 1026 | 1027 | ```ts 1028 | import { $PropertyType } from 'utility-types'; 1029 | 1030 | type Props = { name: string; age: number; visible: boolean }; 1031 | // Expect: string 1032 | type NameType = $PropertyType; 1033 | 1034 | type Tuple = [boolean, number]; 1035 | // Expect: boolean 1036 | type A = $PropertyType; 1037 | // Expect: number 1038 | type B = $PropertyType; 1039 | ``` 1040 | 1041 | [⇧ back to top](#flows-utility-types) 1042 | 1043 | ### `$ElementType` 1044 | 1045 | get the type of elements inside of array, tuple or object of type `T`, that matches the given index type `K`
1046 | https://flow.org/en/docs/types/utilities/#toc-elementtype 1047 | 1048 | **Usage:** 1049 | 1050 | ```ts 1051 | import { $ElementType } from 'utility-types'; 1052 | 1053 | type Props = { name: string; age: number; visible: boolean }; 1054 | // Expect: string 1055 | type NameType = $ElementType; 1056 | 1057 | type Tuple = [boolean, number]; 1058 | // Expect: boolean 1059 | type A = $ElementType; 1060 | // Expect: number 1061 | type B = $ElementType; 1062 | 1063 | type Arr = boolean[]; 1064 | // Expect: boolean 1065 | type ItemsType = $ElementType; 1066 | 1067 | type Obj = { [key: string]: number }; 1068 | // Expect: number 1069 | type ValuesType = $ElementType; 1070 | ``` 1071 | 1072 | [⇧ back to top](#flows-utility-types) 1073 | 1074 | ### `$Call` 1075 | 1076 | get the return type of a given expression type
1077 | https://flow.org/en/docs/types/utilities/#toc-call 1078 | 1079 | The built-in [`ReturnType`](https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype) can be used to accomplish the same goal, although it may have some subtle differences. 1080 | 1081 | **Usage:** 1082 | 1083 | ```ts 1084 | import { $Call } from 'utility-types'; 1085 | 1086 | // Common use-case 1087 | const add = (amount: number) => ({ type: 'ADD' as 'ADD', payload: amount }); 1088 | type AddAction = $Call; // { type: 'ADD'; payload: number } 1089 | 1090 | // Examples migrated from Flow docs 1091 | type ExtractPropType = (arg: T) => T['prop']; 1092 | type Obj = { prop: number }; 1093 | type PropType = $Call>; // number 1094 | // type Nope = $Call>; // Error: argument doesn't match `Obj`. 1095 | 1096 | type ExtractReturnType any> = (arg: T) => ReturnType; 1097 | type Fn = () => number; 1098 | type FnReturnType = $Call>; // number 1099 | ``` 1100 | 1101 | [⇧ back to top](#flows-utility-types) 1102 | 1103 | ### `$Shape` 1104 | 1105 | Copies the shape of the type supplied, but marks every field optional.
1106 | https://flow.org/en/docs/types/utilities/#toc-shape 1107 | 1108 | **Usage:** 1109 | 1110 | ```ts 1111 | import { $Shape } from 'utility-types'; 1112 | 1113 | type Props = { name: string; age: number; visible: boolean }; 1114 | 1115 | // Expect: Partial 1116 | type PartialProps = $Shape; 1117 | ``` 1118 | 1119 | [⇧ back to top](#flows-utility-types) 1120 | 1121 | ### `$NonMaybeType` 1122 | 1123 | Converts a type `T` to a non-maybe type. In other words, the values of `$NonMaybeType` are the values of `T` except for `null` and `undefined`.
1124 | https://flow.org/en/docs/types/utilities/#toc-nonmaybe 1125 | 1126 | **Usage:** 1127 | 1128 | ```ts 1129 | import { $NonMaybeType } from 'utility-types'; 1130 | 1131 | type MaybeName = string | null; 1132 | 1133 | // Expect: string 1134 | type Name = $NonMaybeType; 1135 | ``` 1136 | 1137 | [⇧ back to top](#flows-utility-types) 1138 | 1139 | ### `Class` 1140 | 1141 | Given a type T representing instances of a class C, the type Class is the type of the class C
1142 | https://flow.org/en/docs/types/utilities/#toc-class 1143 | \* Differs from original Flow's util - implements only constructor part and won't include any static members. Additionally classes in Typescript are not treated as nominal 1144 | 1145 | **Usage:** 1146 | 1147 | ```ts 1148 | import { Class } from 'utility-types'; 1149 | 1150 | 1151 | function makeStore(storeClass: Class): Store { 1152 | return new storeClass(); 1153 | } 1154 | ``` 1155 | 1156 | [⇧ back to top](#flows-utility-types) 1157 | 1158 | ### mixed 1159 | 1160 | An arbitrary type that could be anything (same as `unknown`)
1161 | https://flow.org/en/docs/types/mixed 1162 | 1163 | [⇧ back to top](#table-of-contents) 1164 | 1165 | --- 1166 | 1167 | ## Related Projects 1168 | 1169 | - [`ts-toolbelt`](https://github.com/pirix-gh/ts-toolbelt) - Higher type safety for TypeScript 1170 | - [`$mol_type`](https://github.com/eigenmethod/mol/tree/master/type) - Collection of TypeScript meta types for complex logic 1171 | 1172 | --- 1173 | 1174 | ## License 1175 | 1176 | [MIT License](/LICENSE) 1177 | 1178 | Copyright (c) 2016 Piotr Witek (http://piotrwitek.github.io) 1179 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Check below to find which versions are currently being supported with security updates. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 3.x | :white_check_mark: | 10 | | < 3.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | You can report a security vulnerability to this email: piotrek.witek@gmail.com 15 | 16 | You'll get a response back to your email shortly after we read your report. 17 | After analysis of vulnerability is completed we will send you the results and 18 | keep you updated about the next steps. 19 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## Community 4 | 5 | If you're looking for support the best place is to check our spectrum community chat: 6 | - https://spectrum.chat/utility-types 7 | 8 | ## Project Updates 9 | 10 | You can also subscribe to receive project updates on my Twitter and BuyMeACoffee profile: 11 | - https://twitter.com/piotrekwitek 12 | - https://www.buymeacoffee.com/piotrekwitek 13 | -------------------------------------------------------------------------------- /experimental/experimental.txt: -------------------------------------------------------------------------------- 1 | // type Unpacked = 2 | // T extends Array<(infer U)> ? U : 3 | // T extends (...args: any[]) => infer U ? U : 4 | // T extends Promise ? U : 5 | // T; 6 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "testEnvironment": "node", 4 | "moduleFileExtensions": ["js", "jsx", "ts", "tsx", "json"], 5 | "testMatch": ["/src/?(*.)+(spec|test).ts?(x)"], 6 | "transform": { 7 | ".spec.(ts|tsx)": "dts-jest/transform", 8 | ".(ts|tsx)": "ts-jest" 9 | }, 10 | "globals": { 11 | "window": {}, 12 | "_dts_jest_": { 13 | "compiler_options": "./tsconfig.json", 14 | "enclosing_declaration": true, 15 | "test_value": true, 16 | "transpile": true 17 | } 18 | }, 19 | "reporters": ["default", "dts-jest/reporter"] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utility-types", 3 | "version": "3.11.0", 4 | "description": "Utility Types Collection for TypeScript", 5 | "author": "Piotr Witek (http://piotrwitek.github.io)", 6 | "repository": "https://github.com/piotrwitek/utility-types", 7 | "homepage": "https://github.com/piotrwitek/utility-types", 8 | "license": "MIT", 9 | "types": "dist/index.d.ts", 10 | "main": "dist/index.js", 11 | "engines": { 12 | "node": ">= 4" 13 | }, 14 | "husky": { 15 | "hooks": { 16 | "pre-push": "npm run prettier:fix && npm run lint && npm run tsc && npm run test:update" 17 | } 18 | }, 19 | "scripts": { 20 | "ci-check": "npm run prettier && npm run lint && npm run tsc && npm run test", 21 | "reinstall": "rm -rf node_modules/ dist/ && npm install", 22 | "prettier": "prettier --list-different 'src/**/*.ts' || (echo '\nPlease fix code formatting by running:\nnpm run prettier:fix\n'; exit 1)", 23 | "prettier:fix": "prettier --write src/**/*.ts", 24 | "lint": "tslint --project ./tsconfig.json", 25 | "tsc": "tsc -p . --noEmit", 26 | "tsc:watch": "tsc -p . --noEmit -w", 27 | "test": "jest --config jest.config.json && dts-jest-remap ./src/*.spec.ts --rename {{basename}}.snap.{{extname}} --check", 28 | "test:update": "jest --config jest.config.json --no-cache -u && dts-jest-remap ./src/*.spec.ts --rename {{basename}}.snap.{{extname}}", 29 | "test:watch": "jest --config jest.config.json --watch", 30 | "prebuild": "rm -rf dist/", 31 | "build": "tsc -p ./tsconfig.build.json --outDir dist/", 32 | "prepublishOnly": "npm run reinstall && npm run ci-check && npm run build" 33 | }, 34 | "dependencies": {}, 35 | "devDependencies": { 36 | "@types/jest": "24.0.22", 37 | "dts-jest": "23.0.0", 38 | "husky": "3.0.9", 39 | "jest": "24.9.0", 40 | "prettier": "1.19.0", 41 | "ts-jest": "24.1.0", 42 | "tslint": "5.20.1", 43 | "typescript": "3.7.2" 44 | }, 45 | "keywords": [ 46 | "typescript", 47 | "utility", 48 | "types", 49 | "static-typing", 50 | "mapped-types", 51 | "flow", 52 | "flow-typed" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/__snapshots__/aliases-and-guards.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Falsy testType() (type) should match snapshot 1`] = `"Falsy"`; 4 | 5 | exports[`Primitive testType() (type) should match snapshot 1`] = `"Primitive"`; 6 | 7 | exports[`isFalsy param (type) should match snapshot 1`] = `"false | 0 | null | undefined"`; 8 | 9 | exports[`isFalsy param (type) should match snapshot 2`] = `"string"`; 10 | 11 | exports[`isNullish param (type) should match snapshot 1`] = `"Nullish"`; 12 | 13 | exports[`isNullish param (type) should match snapshot 2`] = `"string"`; 14 | 15 | exports[`isPrimitive param (type) should match snapshot 1`] = `"Primitive"`; 16 | 17 | exports[`isPrimitive param (type) should match snapshot 2`] = `"Primitive[]"`; 18 | -------------------------------------------------------------------------------- /src/__snapshots__/functional-helpers.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getReturnOfExpression getReturnOfExpression(increment) (type) should match snapshot 1`] = `"{ type: \\"INCREMENT\\"; }"`; 4 | -------------------------------------------------------------------------------- /src/__snapshots__/mapped-types.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Assign const result: Assign<{}, Omit> = rest (type) should match snapshot 1`] = `"any"`; 4 | 5 | exports[`Assign testType>() (type) should match snapshot 1`] = `"Pick & Pick & Pick, \\"name\\" | \\"age\\" | \\"other\\" | \\"visible\\">"`; 6 | 7 | exports[`AugmentedRequired testType, 'age' | 'visible'>>({ 8 | age: 99, 9 | visible: true, 10 | }) (type) should match snapshot 1`] = `"AugmentedRequired, \\"age\\" | \\"visible\\">"`; 11 | 12 | exports[`AugmentedRequired testType>>({ 13 | name: 'Yolo', 14 | age: 99, 15 | visible: true, 16 | }) (type) should match snapshot 1`] = `"AugmentedRequired, \\"name\\" | \\"age\\" | \\"visible\\">"`; 17 | 18 | exports[`Brand testType>() (type) should match snapshot 1`] = `"Brand"`; 19 | 20 | exports[`DeepNonNullable testType< 21 | DeepNonNullable['first']['second'][number]['name'] 22 | >() (type) should match snapshot 1`] = `"string"`; 23 | 24 | exports[`DeepNonNullable testType< 25 | ReturnType['first']['second']> 26 | >() (type) should match snapshot 1`] = `"string"`; 27 | 28 | exports[`DeepNonNullable testType['first']>() (type) should match snapshot 1`] = `"_DeepNonNullableObject<{ second?: ({ name?: string | null | undefined; } | null | undefined)[] | undefined; }>"`; 29 | 30 | exports[`DeepNonNullable testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepNonNullableArray<{ name?: string | null | undefined; } | null | undefined>"`; 31 | 32 | exports[`DeepNonNullable testType['first']>() (type) should match snapshot 1`] = `"_DeepNonNullableObject<{ second?: ((value: number) => string) | undefined; }>"`; 33 | 34 | exports[`DeepNonNullable testType['first']['second']>() (type) should match snapshot 1`] = `"(value: number) => string"`; 35 | 36 | exports[`DeepNonNullable testType['first']>() (type) should match snapshot 1`] = `"_DeepNonNullableObject<{ second?: { name?: string | null | undefined; } | null | undefined; }>"`; 37 | 38 | exports[`DeepNonNullable testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepNonNullableObject<{ name?: string | null | undefined; }>"`; 39 | 40 | exports[`DeepNonNullable testType['first']['second']['name']>() (type) should match snapshot 1`] = `"string"`; 41 | 42 | exports[`DeepPartial testType>({}) (type) should match snapshot 1`] = `"DeepPartial<{ first: { second: { name: string; }; }; }>"`; 43 | 44 | exports[`DeepPartial testType>({}) (type) should match snapshot 1`] = `"DeepPartial"`; 45 | 46 | exports[`DeepPartial testType>>() (type) should match snapshot 1`] = `"string"`; 47 | 48 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"string | undefined"`; 49 | 50 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"_DeepPartialArray<{ name: string; }> | undefined"`; 51 | 52 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"((value: number) => string) | undefined"`; 53 | 54 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"string | undefined"`; 55 | 56 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"DeepPartial<{ second: { name: string; }[]; }> | undefined"`; 57 | 58 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"DeepPartial<{ second: (value: number) => string; }> | undefined"`; 59 | 60 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"DeepPartial<{ second: { name: string; }; }> | undefined"`; 61 | 62 | exports[`DeepPartial testType() (type) should match snapshot 1`] = `"DeepPartial<{ name: string; }> | undefined"`; 63 | 64 | exports[`DeepReadonly testType>>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ first: { second: { name: string; }[]; }; }>"`; 65 | 66 | exports[`DeepReadonly testType>>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ first: { second: { name: string; }; }; }>"`; 67 | 68 | exports[`DeepReadonly testType['first']>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ second: { name: string; }[]; }>"`; 69 | 70 | exports[`DeepReadonly testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepReadonlyArray<{ name: string; }>"`; 71 | 72 | exports[`DeepReadonly testType['first']['second'][number]['name']>() (type) should match snapshot 1`] = `"string"`; 73 | 74 | exports[`DeepReadonly testType['first']>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ second: (value: number) => string; }>"`; 75 | 76 | exports[`DeepReadonly testType['first']['second']>() (type) should match snapshot 1`] = `"(value: number) => string"`; 77 | 78 | exports[`DeepReadonly testType['first']>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ second: { name: string; }; }>"`; 79 | 80 | exports[`DeepReadonly testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ name: string; }>"`; 81 | 82 | exports[`DeepReadonly testType['first']['second']['name']>() (type) should match snapshot 1`] = `"string"`; 83 | 84 | exports[`DeepReadonly testType>() (type) should match snapshot 1`] = `"string | number | bigint | boolean | symbol | null"`; 85 | 86 | exports[`DeepReadonly testType['first']['second']>>() (type) should match snapshot 1`] = `"string"`; 87 | 88 | exports[`DeepRequired testType['first']>() (type) should match snapshot 1`] = `"_DeepRequiredObject<{ second?: ({ name?: string | null | undefined; } | undefined)[] | undefined; }>"`; 89 | 90 | exports[`DeepRequired testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepRequiredArray<{ name?: string | null | undefined; } | undefined>"`; 91 | 92 | exports[`DeepRequired testType['first']['second'][number]['name']>() (type) should match snapshot 1`] = `"string | null"`; 93 | 94 | exports[`DeepRequired testType['first']>() (type) should match snapshot 1`] = `"_DeepRequiredObject<{ second?: ((value: number) => string) | undefined; }>"`; 95 | 96 | exports[`DeepRequired testType['first']['second']>() (type) should match snapshot 1`] = `"(value: number) => string"`; 97 | 98 | exports[`DeepRequired testType['first']>() (type) should match snapshot 1`] = `"_DeepRequiredObject<{ second?: { name?: string | null | undefined; } | undefined; }>"`; 99 | 100 | exports[`DeepRequired testType['first']['second']>() (type) should match snapshot 1`] = `"_DeepRequiredObject<{ name?: string | null | undefined; }>"`; 101 | 102 | exports[`DeepRequired testType['first']['second']['name']>() (type) should match snapshot 1`] = `"string | null"`; 103 | 104 | exports[`DeepRequired testType['first']['second']>>() (type) should match snapshot 1`] = `"string"`; 105 | 106 | exports[`Diff const result: Diff> = rest (type) should match snapshot 1`] = `"any"`; 107 | 108 | exports[`Diff testType>() (type) should match snapshot 1`] = `"Pick"`; 109 | 110 | exports[`FunctionKeys testType>() (type) should match snapshot 1`] = `"\\"setName\\" | \\"someFn\\""`; 111 | 112 | exports[`Intersection const result: Intersection> = rest (type) should match snapshot 1`] = `"any"`; 113 | 114 | exports[`Intersection testType>() (type) should match snapshot 1`] = `"Pick"`; 115 | 116 | exports[`Intersection testType>() (type) should match snapshot 1`] = `"Pick"`; 117 | 118 | exports[`Mutable testType>>({ 119 | name: 'Yolo', 120 | age: 99, 121 | visible: true, 122 | }) (type) should match snapshot 1`] = `"Mutable>"`; 123 | 124 | exports[`Mutable testType>['age']>(99) (type) should match snapshot 1`] = `"number"`; 125 | 126 | exports[`Mutable testType>['name']>('Yolo') (type) should match snapshot 1`] = `"string"`; 127 | 128 | exports[`Mutable testType>['visible']>(true) (type) should match snapshot 1`] = `"boolean"`; 129 | 130 | exports[`MutableKeys testType>() (type) should match snapshot 1`] = `"\\"b\\""`; 131 | 132 | exports[`NonFunctionKeys testType>() (type) should match snapshot 1`] = `"\\"name\\" | \\"someKeys\\""`; 133 | 134 | exports[`NonUndefined testType>() (type) should match snapshot 1`] = `"string | null"`; 135 | 136 | exports[`NonUndefined testType>() (type) should match snapshot 1`] = `"never"`; 137 | 138 | exports[`Omit const result: Omit = rest (type) should match snapshot 1`] = `"any"`; 139 | 140 | exports[`Omit testType>() (type) should match snapshot 1`] = `"Pick"`; 141 | 142 | exports[`Omit testType>() (type) should match snapshot 1`] = `"Pick"`; 143 | 144 | exports[`Omit testType>() (type) should match snapshot 1`] = `"Pick>"`; 145 | 146 | exports[`OmitByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 147 | 148 | exports[`OmitByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 149 | 150 | exports[`OmitByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 151 | 152 | exports[`OmitByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 153 | 154 | exports[`OmitByValue testType>() (type) should match snapshot 1`] = `"\\"reqUndef\\" | \\"opt\\" | \\"optUndef\\""`; 155 | 156 | exports[`OmitByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 157 | 158 | exports[`OmitByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 159 | 160 | exports[`OmitByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 161 | 162 | exports[`OmitByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 163 | 164 | exports[`OmitByValueExact testType>() (type) should match snapshot 1`] = `"\\"reqUndef\\" | \\"opt\\" | \\"optUndef\\""`; 165 | 166 | exports[`Optional testType>({ name: 'Yolo' }) (type) should match snapshot 1`] = `"Optional"`; 167 | 168 | exports[`Optional testType>({ name: 'Yolo', age: 99 }) (type) should match snapshot 1`] = `"Optional"`; 169 | 170 | exports[`Optional testType>({ age: 99 }) (type) should match snapshot 1`] = `"Optional"`; 171 | 172 | exports[`Optional testType>({}) (type) should match snapshot 1`] = `"Optional"`; 173 | 174 | exports[`OptionalKeys testType>() (type) should match snapshot 1`] = `"\\"opt\\" | \\"optUndef\\""`; 175 | 176 | exports[`Overwrite const result: Overwrite, T> = rest (type) should match snapshot 1`] = `"any"`; 177 | 178 | exports[`Overwrite testType>() (type) should match snapshot 1`] = `"Pick & Pick, \\"name\\" | \\"age\\" | \\"visible\\">"`; 179 | 180 | exports[`PickByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 181 | 182 | exports[`PickByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 183 | 184 | exports[`PickByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 185 | 186 | exports[`PickByValue testType>() (type) should match snapshot 1`] = `"Pick"`; 187 | 188 | exports[`PickByValue testType>() (type) should match snapshot 1`] = `"\\"req\\""`; 189 | 190 | exports[`PickByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 191 | 192 | exports[`PickByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 193 | 194 | exports[`PickByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 195 | 196 | exports[`PickByValueExact testType>() (type) should match snapshot 1`] = `"Pick"`; 197 | 198 | exports[`PickByValueExact testType>() (type) should match snapshot 1`] = `"\\"req\\""`; 199 | 200 | exports[`PromiseType testType>>() (type) should match snapshot 1`] = `"string"`; 201 | 202 | exports[`ReadonlyKeys testType>() (type) should match snapshot 1`] = `"\\"a\\""`; 203 | 204 | exports[`RequiredKeys testType>() (type) should match snapshot 1`] = `"\\"req\\" | \\"reqUndef\\""`; 205 | 206 | exports[`SetComplement testType>() (type) should match snapshot 1`] = `"\\"1\\""`; 207 | 208 | exports[`SetDifference testType>() (type) should match snapshot 1`] = `"\\"1\\""`; 209 | 210 | exports[`SetDifference testType void), () => void>>() (type) should match snapshot 1`] = `"string | number"`; 211 | 212 | exports[`SetIntersection testType>() (type) should match snapshot 1`] = `"\\"2\\" | \\"3\\""`; 213 | 214 | exports[`SetIntersection testType void), () => void>>() (type) should match snapshot 1`] = `"() => void"`; 215 | 216 | exports[`Subtract const result: Subtract> = rest (type) should match snapshot 1`] = `"any"`; 217 | 218 | exports[`Subtract testType>() (type) should match snapshot 1`] = `"Pick"`; 219 | 220 | exports[`SymmetricDifference testType>() (type) should match snapshot 1`] = `"\\"1\\" | \\"4\\""`; 221 | 222 | exports[`UnionKeys testType>() (type) should match snapshot 1`] = `"\\"name\\" | \\"age\\" | \\"other\\" | \\"visible\\""`; 223 | 224 | exports[`UnionToIntersection testType< 225 | UnionToIntersection< 226 | { name: string } | { age: number } | { visible: boolean } 227 | > 228 | >({ 229 | name: 'Yolo', 230 | age: 99, 231 | visible: true, 232 | }) (type) should match snapshot 1`] = `"{ name: string; } & { age: number; } & { visible: boolean; }"`; 233 | 234 | exports[`UnionToIntersection testType>() (type) should match snapshot 1`] = `"never"`; 235 | 236 | exports[`UnionToIntersection testType>() (type) should match snapshot 1`] = `"never"`; 237 | 238 | exports[`UnionToIntersection testType>() (type) should match snapshot 1`] = `"never"`; 239 | 240 | exports[`Unionize testType>() (type) should match snapshot 1`] = `"{ name: string; } | { age: number; } | { visible: boolean; }"`; 241 | 242 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"1 | 2"`; 243 | 244 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"string | number | boolean"`; 245 | 246 | exports[`ValuesType testType>>() (type) should match snapshot 1`] = `"string"`; 247 | 248 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"number"`; 249 | 250 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"number"`; 251 | 252 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"number"`; 253 | 254 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"number"`; 255 | 256 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"1 | 2"`; 257 | 258 | exports[`ValuesType testType>() (type) should match snapshot 1`] = `"symbol"`; 259 | -------------------------------------------------------------------------------- /src/__snapshots__/utility-types.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`$Call testType<$Call<(amount: number) => { type: 'ADD'; payload: number }>>() (type) should match snapshot 1`] = `"{ type: \\"ADD\\"; payload: number; }"`; 4 | 5 | exports[`$Call testType() (type) should match snapshot 1`] = `"number"`; 6 | 7 | exports[`$Call testType() (type) should match snapshot 1`] = `"number"`; 8 | 9 | exports[`$Diff testType<$Diff>() (type) should match snapshot 1`] = `"Pick"`; 10 | 11 | exports[`$ElementType testType<$ElementType<[boolean, number], 0>>() (type) should match snapshot 1`] = `"boolean"`; 12 | 13 | exports[`$ElementType testType<$ElementType<[boolean, number], 1>>() (type) should match snapshot 1`] = `"number"`; 14 | 15 | exports[`$ElementType testType<$ElementType<{ [key: string]: number }, string>>() (type) should match snapshot 1`] = `"number"`; 16 | 17 | exports[`$ElementType testType<$ElementType>() (type) should match snapshot 1`] = `"string"`; 18 | 19 | exports[`$ElementType testType<$ElementType>() (type) should match snapshot 1`] = `"boolean"`; 20 | 21 | exports[`$Keys testType<$Keys>() (type) should match snapshot 1`] = `"\\"name\\" | \\"age\\" | \\"visible\\""`; 22 | 23 | exports[`$NonMaybeType testType<$NonMaybeType>() (type) should match snapshot 1`] = `"string"`; 24 | 25 | exports[`$PropertyType testType<$PropertyType<[boolean, number], '0'>>() (type) should match snapshot 1`] = `"boolean"`; 26 | 27 | exports[`$PropertyType testType<$PropertyType<[boolean, number], '1'>>() (type) should match snapshot 1`] = `"number"`; 28 | 29 | exports[`$PropertyType testType<$PropertyType>() (type) should match snapshot 1`] = `"string"`; 30 | 31 | exports[`$ReadOnly testType<$ReadOnly>() (type) should match snapshot 1`] = `"_DeepReadonlyObject<{ name: string; age: number; visible: boolean; }>"`; 32 | 33 | exports[`$Shape testType<$Shape>() (type) should match snapshot 1`] = `"Partial"`; 34 | 35 | exports[`$Values testType<$Values>() (type) should match snapshot 1`] = `"string | number | boolean"`; 36 | 37 | exports[`Class testType>() (type) should match snapshot 1`] = `"Class"`; 38 | 39 | exports[`mixed testType() (type) should match snapshot 1`] = `"unknown"`; 40 | -------------------------------------------------------------------------------- /src/aliases-and-guards.snap.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { Primitive, isPrimitive, Falsy, isFalsy } from './type-guards'; 3 | 4 | // @dts-jest:group Primitive 5 | { 6 | // @dts-jest:pass:snap -> Primitive 7 | testType(); 8 | } 9 | 10 | // @dts-jest:group isPrimitive 11 | it('narrows to correct type', () => { 12 | const consumer = (param: Primitive[] | Primitive): string => { 13 | if (isPrimitive(param)) { 14 | // @dts-jest:pass:snap -> Primitive 15 | param; 16 | return String(param) + ' was Primitive'; 17 | } 18 | // @dts-jest:pass:snap -> Primitive[] 19 | param; 20 | const resultArray = param 21 | .map(consumer) 22 | .map(rootString => '\n\t' + rootString); 23 | return resultArray.reduce((comm, newV) => comm + newV, 'this was nested:'); 24 | }; 25 | }); 26 | 27 | // @dts-jest:group Falsy 28 | { 29 | // @dts-jest:pass:snap -> Falsy 30 | testType(); 31 | } 32 | 33 | // @dts-jest:group isFalsy-Falsy 34 | it('returns true for falsy and narrows type', () => { 35 | const falsyTestVals: unknown[] = ['', null, undefined, false, 0]; 36 | 37 | falsyTestVals.forEach(val => { 38 | if (isFalsy(val)) { 39 | // @dts-jest:pass:snap -> Falsy 40 | val; 41 | } 42 | // @dts-jest:pass:snap -> unknown 43 | val; 44 | }); 45 | 46 | const testResults = falsyTestVals.map(isFalsy); 47 | 48 | testResults.forEach(val => expect(val).toBe(true)); 49 | }); 50 | 51 | // @dts-jest:group isFalsy-Truthy 52 | it('returns false for truthy and narrows type', () => { 53 | const truthyTestVals: unknown[] = [' ', true, {}, []]; 54 | 55 | truthyTestVals.forEach(val => { 56 | if (isFalsy(val)) { 57 | // @dts-jest:pass:snap -> Falsy 58 | val; 59 | } 60 | // @dts-jest:pass:snap -> unknown 61 | val; 62 | }); 63 | 64 | const testResults = truthyTestVals.map(isFalsy); 65 | 66 | testResults.forEach(val => expect(val).toBe(false)); 67 | }); 68 | -------------------------------------------------------------------------------- /src/aliases-and-guards.spec.snap.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | Primitive, 4 | isPrimitive, 5 | Falsy, 6 | isFalsy, 7 | Nullish, 8 | isNullish, 9 | } from './aliases-and-guards'; 10 | 11 | // @dts-jest:group Primitive 12 | { 13 | // @dts-jest:pass:snap -> Primitive 14 | testType(); 15 | } 16 | 17 | // @dts-jest:group isPrimitive 18 | it('narrows to correct type', () => { 19 | const consumer = (param: Primitive[] | Primitive): string => { 20 | if (isPrimitive(param)) { 21 | // @dts-jest:pass:snap -> Primitive 22 | param; 23 | return String(param) + ' was Primitive'; 24 | } 25 | // @dts-jest:pass:snap -> Primitive[] 26 | param; 27 | const resultArray = param 28 | .map(consumer) 29 | .map(rootString => '\n\t' + rootString); 30 | return resultArray.reduce((comm, newV) => comm + newV, 'this was nested:'); 31 | }; 32 | }); 33 | 34 | // @dts-jest:group Falsy 35 | { 36 | // @dts-jest:pass:snap -> Falsy 37 | testType(); 38 | } 39 | 40 | // @dts-jest:group isFalsy 41 | it('narrows to correct type', () => { 42 | const consumer = (param: Falsy | string): string => { 43 | if (isFalsy(param)) { 44 | // @dts-jest:pass:snap -> false | 0 | null | undefined 45 | param; 46 | return String(param) + ' was Falsy'; 47 | } 48 | // @dts-jest:pass:snap -> string 49 | param; 50 | return param.toString(); 51 | }; 52 | }); 53 | 54 | // @dts-jest:group isFalsy - test falsy values 55 | it('returns true for falsy', () => { 56 | const falsyTestVals: unknown[] = [false, '', 0, null, undefined]; 57 | 58 | const testResults = falsyTestVals.map(isFalsy); 59 | testResults.forEach(val => expect(val).toBe(true)); 60 | }); 61 | 62 | // @dts-jest:group isFalsy - test truthy values 63 | it('returns false for truthy', () => { 64 | const truthyTestVals: unknown[] = [' ', true, {}, []]; 65 | 66 | const testResults = truthyTestVals.map(isFalsy); 67 | testResults.forEach(val => expect(val).toBe(false)); 68 | }); 69 | 70 | // @dts-jest:group isNullish 71 | it('narrows to correct type', () => { 72 | const consumer = (param: Nullish | string): string => { 73 | if (isNullish(param)) { 74 | // @dts-jest:pass:snap -> Nullish 75 | param; 76 | return String(param) + ' was Nullish'; 77 | } 78 | // @dts-jest:pass:snap -> string 79 | param; 80 | return param.toString(); 81 | }; 82 | }); 83 | 84 | // @dts-jest:group isNullish - test nullish values 85 | it('returns true for nullish', () => { 86 | const nullishTestVals: unknown[] = [null, undefined]; 87 | 88 | const testResults = nullishTestVals.map(isNullish); 89 | testResults.forEach(val => expect(val).toBe(true)); 90 | }); 91 | 92 | // @dts-jest:group isNullish - test non-nullish values 93 | it('returns false for non-nullish', () => { 94 | const nonNullishTestVals: unknown[] = [false, '', 0, ' ', true, {}, []]; 95 | 96 | const testResults = nonNullishTestVals.map(isNullish); 97 | testResults.forEach(val => expect(val).toBe(false)); 98 | }); 99 | -------------------------------------------------------------------------------- /src/aliases-and-guards.spec.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | Primitive, 4 | isPrimitive, 5 | Falsy, 6 | isFalsy, 7 | Nullish, 8 | isNullish, 9 | } from './aliases-and-guards'; 10 | 11 | // @dts-jest:group Primitive 12 | { 13 | // @dts-jest:pass:snap 14 | testType(); 15 | } 16 | 17 | // @dts-jest:group isPrimitive 18 | it('narrows to correct type', () => { 19 | const consumer = (param: Primitive[] | Primitive): string => { 20 | if (isPrimitive(param)) { 21 | // @dts-jest:pass:snap 22 | param; 23 | return String(param) + ' was Primitive'; 24 | } 25 | // @dts-jest:pass:snap 26 | param; 27 | const resultArray = param 28 | .map(consumer) 29 | .map(rootString => '\n\t' + rootString); 30 | return resultArray.reduce((comm, newV) => comm + newV, 'this was nested:'); 31 | }; 32 | }); 33 | 34 | // @dts-jest:group Falsy 35 | { 36 | // @dts-jest:pass:snap 37 | testType(); 38 | } 39 | 40 | // @dts-jest:group isFalsy 41 | it('narrows to correct type', () => { 42 | const consumer = (param: Falsy | string): string => { 43 | if (isFalsy(param)) { 44 | // @dts-jest:pass:snap 45 | param; 46 | return String(param) + ' was Falsy'; 47 | } 48 | // @dts-jest:pass:snap 49 | param; 50 | return param.toString(); 51 | }; 52 | }); 53 | 54 | // @dts-jest:group isFalsy - test falsy values 55 | it('returns true for falsy', () => { 56 | const falsyTestVals: unknown[] = [false, '', 0, null, undefined]; 57 | 58 | const testResults = falsyTestVals.map(isFalsy); 59 | testResults.forEach(val => expect(val).toBe(true)); 60 | }); 61 | 62 | // @dts-jest:group isFalsy - test truthy values 63 | it('returns false for truthy', () => { 64 | const truthyTestVals: unknown[] = [' ', true, {}, []]; 65 | 66 | const testResults = truthyTestVals.map(isFalsy); 67 | testResults.forEach(val => expect(val).toBe(false)); 68 | }); 69 | 70 | // @dts-jest:group isNullish 71 | it('narrows to correct type', () => { 72 | const consumer = (param: Nullish | string): string => { 73 | if (isNullish(param)) { 74 | // @dts-jest:pass:snap 75 | param; 76 | return String(param) + ' was Nullish'; 77 | } 78 | // @dts-jest:pass:snap 79 | param; 80 | return param.toString(); 81 | }; 82 | }); 83 | 84 | // @dts-jest:group isNullish - test nullish values 85 | it('returns true for nullish', () => { 86 | const nullishTestVals: unknown[] = [null, undefined]; 87 | 88 | const testResults = nullishTestVals.map(isNullish); 89 | testResults.forEach(val => expect(val).toBe(true)); 90 | }); 91 | 92 | // @dts-jest:group isNullish - test non-nullish values 93 | it('returns false for non-nullish', () => { 94 | const nonNullishTestVals: unknown[] = [false, '', 0, ' ', true, {}, []]; 95 | 96 | const testResults = nonNullishTestVals.map(isNullish); 97 | testResults.forEach(val => expect(val).toBe(false)); 98 | }); 99 | -------------------------------------------------------------------------------- /src/aliases-and-guards.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Primitive 3 | * @desc Type representing [`Primitive`](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) types in TypeScript: `string | number | bigint | boolean | symbol | null | undefined` 4 | * @example 5 | * type Various = number | string | object; 6 | * 7 | * // Expect: object 8 | * type Cleaned = Exclude 9 | */ 10 | export type Primitive = 11 | | string 12 | | number 13 | | bigint 14 | | boolean 15 | | symbol 16 | | null 17 | | undefined; 18 | 19 | /** 20 | * Falsy 21 | * @desc Type representing falsy values in TypeScript: `false | "" | 0 | null | undefined` 22 | * @example 23 | * type Various = 'a' | 'b' | undefined | false; 24 | * 25 | * // Expect: "a" | "b" 26 | * Exclude; 27 | */ 28 | export type Falsy = false | '' | 0 | null | undefined; 29 | 30 | /** 31 | * Nullish 32 | * @desc Type representing [nullish values][https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing] in TypeScript: `null | undefined` 33 | * @example 34 | * type Various = 'a' | 'b' | undefined; 35 | * 36 | * // Expect: "a" | "b" 37 | * Exclude; 38 | */ 39 | export type Nullish = null | undefined; 40 | 41 | /** 42 | * Tests for one of the [`Primitive`](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) types using the JavaScript [`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) operator 43 | * 44 | * Clarification: TypeScript overloads this operator to produce TypeScript types if used in context of types. 45 | * 46 | * @param val The value to be tested 47 | * @returns If `val` is primitive. If used in the flow of the program typescript will infer type-information from this. 48 | * 49 | * @example 50 | * const consumer = (value: Primitive | Primitive[]) => { 51 | * if (isPrimitive(value)) { 52 | * return console.log('Primitive value: ', value); 53 | * } 54 | * // type of value now inferred as Primitive[] 55 | * value.map((primitive) => consumer(primitive)); 56 | * }; 57 | */ 58 | export const isPrimitive = (val: unknown): val is Primitive => { 59 | if (val === null || val === undefined) { 60 | return true; 61 | } 62 | switch (typeof val) { 63 | case 'string': 64 | case 'number': 65 | case 'bigint': 66 | case 'boolean': 67 | case 'symbol': { 68 | return true; 69 | } 70 | default: 71 | return false; 72 | } 73 | }; 74 | 75 | /** 76 | * Tests for Falsy by simply applying negation `!` to the tested `val`. 77 | * 78 | * The value is mostly in added type-information and explicity, 79 | * but in case of this simple type much the same can often be archived by just using negation `!`: 80 | * @example 81 | * const consumer = (value: boolean | Falsy) => { 82 | * if (!value) { 83 | * return ; 84 | * } 85 | * type newType = typeof value; // === true 86 | * // do stuff 87 | * }; 88 | */ 89 | export const isFalsy = (val: unknown): val is Falsy => !val; 90 | 91 | /** 92 | * Tests for Nullish by simply comparing `val` for equality with `null`. 93 | * @example 94 | * const consumer = (param: Nullish | string): string => { 95 | * if (isNullish(param)) { 96 | * // typeof param === Nullish 97 | * return String(param) + ' was Nullish'; 98 | * } 99 | * // typeof param === string 100 | * return param.toString(); 101 | * }; 102 | */ 103 | export const isNullish = (val: unknown): val is Nullish => val == null; 104 | -------------------------------------------------------------------------------- /src/functional-helpers.spec.snap.ts: -------------------------------------------------------------------------------- 1 | import { getReturnOfExpression } from './functional-helpers'; 2 | 3 | // @dts-jest:group getReturnOfExpression 4 | { 5 | const increment = () => ({ type: 'INCREMENT' as 'INCREMENT' }); 6 | // @dts-jest:pass:snap -> { type: "INCREMENT"; } 7 | getReturnOfExpression(increment); // => undefined 8 | } 9 | -------------------------------------------------------------------------------- /src/functional-helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import { getReturnOfExpression } from './functional-helpers'; 2 | 3 | // @dts-jest:group getReturnOfExpression 4 | { 5 | const increment = () => ({ type: 'INCREMENT' as 'INCREMENT' }); 6 | // @dts-jest:pass:snap 7 | getReturnOfExpression(increment); // => undefined 8 | } 9 | -------------------------------------------------------------------------------- /src/functional-helpers.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Piotr Witek (http://piotrwitek.github.io) 2 | 3 | /** 4 | * @function getReturnOfExpression 5 | * @deprecated from TS v2.8 use built-in ReturnType or $Call API 6 | * @description infer the return type from a given "expression" (at runtime it's equivalent of "noop") 7 | * @template RT - ReturnType 8 | * @param expression: (...params: any[]) => RT 9 | * @returns undefined as RT 10 | */ 11 | export function getReturnOfExpression( 12 | expression: (...params: any[]) => RT 13 | ): RT { 14 | return (undefined as any) as RT; 15 | } 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Piotr Witek (http://piotrwitek.github.io) 3 | * @copyright Copyright (c) 2016 Piotr Witek 4 | * @license MIT 5 | */ 6 | 7 | export { 8 | $Call, 9 | $Diff, 10 | $ElementType, 11 | $Keys, 12 | $NonMaybeType, 13 | $PropertyType, 14 | $ReadOnly, 15 | $Shape, 16 | $Values, 17 | Class, 18 | } from './utility-types'; 19 | 20 | export { 21 | Assign, 22 | Brand, 23 | DeepNonNullable, 24 | DeepPartial, 25 | DeepReadonly, 26 | DeepRequired, 27 | Diff, 28 | FunctionKeys, 29 | Intersection, 30 | Mutable, 31 | MutableKeys, 32 | NonFunctionKeys, 33 | NonUndefined, 34 | Omit, 35 | OmitByValue, 36 | OmitByValueExact, 37 | OptionalKeys, 38 | UnionKeys, 39 | Overwrite, 40 | Optional, 41 | PickByValue, 42 | PickByValueExact, 43 | PromiseType, 44 | ReadonlyKeys, 45 | AugmentedRequired as Required, 46 | RequiredKeys, 47 | SetComplement, 48 | SetDifference, 49 | SetIntersection, 50 | Subtract, 51 | SymmetricDifference, 52 | Unionize, 53 | UnionToIntersection, 54 | ValuesType, 55 | Writable, 56 | WritableKeys, 57 | } from './mapped-types'; 58 | 59 | export { 60 | Falsy, 61 | Falsy as Falsey, // deprecated in v3, backward compatibility until v4 62 | isFalsy, 63 | Nullish, 64 | isNullish, 65 | Primitive, 66 | isPrimitive, 67 | } from './aliases-and-guards'; 68 | 69 | // deprecated 70 | export { getReturnOfExpression } from './functional-helpers'; 71 | -------------------------------------------------------------------------------- /src/mapped-types.spec.snap.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | SetIntersection, 4 | SetDifference, 5 | SetComplement, 6 | SymmetricDifference, 7 | FunctionKeys, 8 | NonUndefined, 9 | NonFunctionKeys, 10 | Omit, 11 | PickByValue, 12 | OmitByValue, 13 | Intersection, 14 | Diff, 15 | Subtract, 16 | Overwrite, 17 | Assign, 18 | Unionize, 19 | PromiseType, 20 | DeepReadonly, 21 | DeepRequired, 22 | DeepNonNullable, 23 | DeepPartial, 24 | MutableKeys, 25 | ReadonlyKeys, 26 | Brand, 27 | _DeepNonNullableArray, 28 | _DeepNonNullableObject, 29 | _DeepReadonlyArray, 30 | _DeepReadonlyObject, 31 | _DeepRequiredArray, 32 | _DeepRequiredObject, 33 | _DeepPartial, 34 | _DeepPartialArray, 35 | RequiredKeys, 36 | OptionalKeys, 37 | UnionKeys, 38 | PickByValueExact, 39 | OmitByValueExact, 40 | Optional, 41 | ValuesType, 42 | AugmentedRequired, 43 | UnionToIntersection, 44 | Mutable, 45 | } from './mapped-types'; 46 | 47 | /** 48 | * Fixtures 49 | */ 50 | 51 | type Props = { name: string; age: number; visible: boolean }; 52 | type DefaultProps = { age: number }; 53 | type NewProps = { age: string; other: string }; 54 | type MixedProps = { 55 | name: string; 56 | setName: (name: string) => void; 57 | someKeys?: string; 58 | someFn?: (...args: any) => any; 59 | }; 60 | type ReadWriteProps = { readonly a: number; b: string }; 61 | type RequiredOptionalProps = { 62 | req: number; 63 | reqUndef: number | undefined; 64 | opt?: string; 65 | optUndef?: string | undefined; 66 | }; 67 | 68 | /** 69 | * Tests 70 | */ 71 | 72 | // @dts-jest:group SetIntersection 73 | { 74 | // @dts-jest:pass:snap -> "2" | "3" 75 | testType>(); 76 | // @dts-jest:pass:snap -> () => void 77 | testType void), () => void>>(); 78 | } 79 | 80 | // @dts-jest:group SetDifference 81 | { 82 | // @dts-jest:pass:snap -> "1" 83 | testType>(); 84 | // @dts-jest:pass:snap -> string | number 85 | testType void), () => void>>(); 86 | } 87 | 88 | // @dts-jest:group SetComplement 89 | { 90 | // @dts-jest:pass:snap -> "1" 91 | testType>(); 92 | } 93 | 94 | // @dts-jest:group SymmetricDifference 95 | { 96 | // @dts-jest:pass:snap -> "1" | "4" 97 | testType>(); 98 | } 99 | 100 | // @dts-jest:group NonUndefined 101 | { 102 | // @dts-jest:pass:snap -> string | null 103 | testType>(); 104 | // @dts-jest:pass:snap -> never 105 | testType>(); 106 | } 107 | 108 | // @dts-jest:group FunctionKeys 109 | { 110 | // @dts-jest:pass:snap -> "setName" | "someFn" 111 | testType>(); 112 | } 113 | 114 | // @dts-jest:group NonFunctionKeys 115 | { 116 | // @dts-jest:pass:snap -> "name" | "someKeys" 117 | testType>(); 118 | } 119 | 120 | // @dts-jest:group MutableKeys 121 | { 122 | // @dts-jest:pass:snap -> "b" 123 | testType>(); 124 | } 125 | 126 | // @dts-jest:group ReadonlyKeys 127 | { 128 | // @dts-jest:pass:snap -> "a" 129 | testType>(); 130 | } 131 | 132 | // @dts-jest:group RequiredKeys 133 | { 134 | // @dts-jest:pass:snap -> "req" | "reqUndef" 135 | testType>(); 136 | } 137 | 138 | // @dts-jest:group OptionalKeys 139 | { 140 | // @dts-jest:pass:snap -> "opt" | "optUndef" 141 | testType>(); 142 | } 143 | 144 | // @dts-jest:group UnionKeys 145 | { 146 | // @dts-jest:pass:snap -> "name" | "age" | "other" | "visible" 147 | testType>(); 148 | } 149 | 150 | // @dts-jest:group PickByValue 151 | { 152 | // @dts-jest:pass:snap -> Pick 153 | testType>(); 154 | // @dts-jest:pass:snap -> Pick 155 | testType>(); 156 | // @dts-jest:pass:snap -> Pick 157 | testType>(); 158 | // @dts-jest:pass:snap -> "req" 159 | testType>(); 160 | 161 | const fn = (props: T) => { 162 | // @dts-jest:pass:snap -> Pick 163 | testType>(); 164 | }; 165 | } 166 | 167 | // @dts-jest:group PickByValueExact 168 | { 169 | // @dts-jest:pass:snap -> Pick 170 | testType>(); 171 | // @dts-jest:pass:snap -> Pick 172 | testType>(); 173 | // @dts-jest:pass:snap -> Pick 174 | testType>(); 175 | // @dts-jest:pass:snap -> "req" 176 | testType>(); 177 | 178 | const fn = (props: T) => { 179 | // @dts-jest:pass:snap -> Pick 180 | testType>(); 181 | }; 182 | } 183 | 184 | // @dts-jest:group Omit 185 | { 186 | // @dts-jest:pass:snap -> Pick 187 | testType>(); 188 | // @dts-jest:pass:snap -> Pick 189 | testType>(); 190 | 191 | const fn = (props: T) => { 192 | // @dts-jest:pass:snap -> Pick> 193 | testType>(); 194 | 195 | const { age, ...rest } = props; 196 | // @dts-jest:pass:snap -> any 197 | const result: Omit = rest; 198 | }; 199 | } 200 | 201 | // @dts-jest:group OmitByValue 202 | { 203 | // @dts-jest:pass:snap -> Pick 204 | testType>(); 205 | // @dts-jest:pass:snap -> Pick 206 | testType>(); 207 | // @dts-jest:pass:snap -> Pick 208 | testType>(); 209 | // @dts-jest:pass:snap -> "reqUndef" | "opt" | "optUndef" 210 | testType>(); 211 | 212 | const fn = (props: T) => { 213 | // @dts-jest:pass:snap -> Pick 214 | testType>(); 215 | }; 216 | } 217 | 218 | // @dts-jest:group OmitByValueExact 219 | { 220 | // @dts-jest:pass:snap -> Pick 221 | testType>(); 222 | // @dts-jest:pass:snap -> Pick 223 | testType>(); 224 | // @dts-jest:pass:snap -> Pick 225 | testType>(); 226 | // @dts-jest:pass:snap -> "reqUndef" | "opt" | "optUndef" 227 | testType>(); 228 | 229 | const fn = (props: T) => { 230 | // @dts-jest:pass:snap -> Pick 231 | testType>(); 232 | }; 233 | } 234 | 235 | // @dts-jest:group Intersection 236 | { 237 | // @dts-jest:pass:snap -> Pick 238 | testType>(); 239 | // @dts-jest:pass:snap -> Pick 240 | testType>(); 241 | 242 | const fn = (props: T) => { 243 | const { age, ...rest } = props; 244 | // @dts-jest:pass:snap -> any 245 | const result: Intersection> = rest; 246 | }; 247 | } 248 | 249 | // @dts-jest:group Diff 250 | { 251 | // @dts-jest:pass:snap -> Pick 252 | testType>(); 253 | 254 | const fn = (props: T) => { 255 | const { age, ...rest } = props; 256 | // @dts-jest:pass:snap -> any 257 | const result: Diff> = rest; 258 | }; 259 | } 260 | 261 | // @dts-jest:group Subtract 262 | { 263 | // @dts-jest:pass:snap -> Pick 264 | testType>(); 265 | 266 | const fn = (props: T) => { 267 | const { age, ...rest } = props; 268 | // @dts-jest:pass:snap -> any 269 | const result: Subtract> = rest; 270 | }; 271 | } 272 | 273 | // @dts-jest:group Overwrite 274 | { 275 | // @dts-jest:pass:snap -> Pick & Pick, "name" | "age" | "visible"> 276 | testType>(); 277 | 278 | const fn = (props: T) => { 279 | const { age, ...rest } = props; 280 | // @dts-jest:pass:snap -> any 281 | const result: Overwrite, T> = rest; 282 | }; 283 | } 284 | 285 | // @dts-jest:group Assign 286 | { 287 | // @dts-jest:pass:snap -> Pick & Pick & Pick, "name" | "age" | "other" | "visible"> 288 | testType>(); 289 | 290 | const fn = (props: T) => { 291 | const { age, ...rest } = props; 292 | // @dts-jest:pass:snap -> any 293 | const result: Assign<{}, Omit> = rest; 294 | }; 295 | } 296 | 297 | // @dts-jest:group Unionize 298 | { 299 | // @dts-jest:pass:snap -> { name: string; } | { age: number; } | { visible: boolean; } 300 | testType>(); 301 | } 302 | 303 | // @dts-jest:group PromiseType 304 | { 305 | // @dts-jest:pass:snap -> string 306 | testType>>(); 307 | } 308 | 309 | // @dts-jest:group DeepReadonly 310 | { 311 | type NestedProps = { 312 | first: { 313 | second: { 314 | name: string; 315 | }; 316 | }; 317 | }; 318 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ second: { name: string; }; }> 319 | testType['first']>(); 320 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ name: string; }> 321 | testType['first']['second']>(); 322 | // @dts-jest:pass:snap -> string 323 | testType['first']['second']['name']>(); 324 | 325 | type NestedArrayProps = { 326 | first: { 327 | second: Array<{ name: string }>; 328 | }; 329 | }; 330 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ second: { name: string; }[]; }> 331 | testType['first']>(); 332 | // @dts-jest:pass:snap -> _DeepReadonlyArray<{ name: string; }> 333 | testType['first']['second']>(); 334 | // @dts-jest:pass:snap -> string 335 | testType['first']['second'][number]['name']>(); 336 | 337 | type NestedFunctionProps = { 338 | first: { 339 | second: (value: number) => string; 340 | }; 341 | }; 342 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ second: (value: number) => string; }> 343 | testType['first']>(); 344 | // @dts-jest:pass:snap -> (value: number) => string 345 | testType['first']['second']>(); 346 | // @dts-jest:pass:snap -> string 347 | testType['first']['second']>>(); 348 | 349 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ first: { second: { name: string; }; }; }> 350 | testType>>(); 351 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ first: { second: { name: string; }[]; }; }> 352 | testType>>(); 353 | 354 | // @dts-jest:pass:snap -> string | number | bigint | boolean | symbol | null 355 | testType>(); 356 | } 357 | 358 | // @dts-jest:group DeepRequired 359 | { 360 | type NestedProps = { 361 | first?: { 362 | second?: { 363 | name?: string | null; 364 | }; 365 | }; 366 | }; 367 | // @dts-jest:pass:snap -> _DeepRequiredObject<{ second?: { name?: string | null | undefined; } | undefined; }> 368 | testType['first']>(); 369 | // @dts-jest:pass:snap -> _DeepRequiredObject<{ name?: string | null | undefined; }> 370 | testType['first']['second']>(); 371 | // @dts-jest:pass:snap -> string | null 372 | testType['first']['second']['name']>(); 373 | 374 | type NestedArrayProps = { 375 | first?: { 376 | second?: Array<{ name?: string | null } | undefined>; 377 | }; 378 | }; 379 | // @dts-jest:pass:snap -> _DeepRequiredObject<{ second?: ({ name?: string | null | undefined; } | undefined)[] | undefined; }> 380 | testType['first']>(); 381 | // @dts-jest:pass:snap -> _DeepRequiredArray<{ name?: string | null | undefined; } | undefined> 382 | testType['first']['second']>(); 383 | // @dts-jest:pass:snap -> string | null 384 | testType['first']['second'][number]['name']>(); 385 | 386 | type NestedFunctionProps = { 387 | first?: { 388 | second?: (value: number) => string; 389 | }; 390 | }; 391 | // @dts-jest:pass:snap -> _DeepRequiredObject<{ second?: ((value: number) => string) | undefined; }> 392 | testType['first']>(); 393 | // @dts-jest:pass:snap -> (value: number) => string 394 | testType['first']['second']>(); 395 | // @dts-jest:pass:snap -> string 396 | testType['first']['second']>>(); 397 | } 398 | 399 | // @dts-jest:group DeepNonNullable 400 | { 401 | type NestedProps = { 402 | first?: null | { 403 | second?: null | { 404 | name?: null | string; 405 | }; 406 | }; 407 | }; 408 | // @dts-jest:pass:snap -> _DeepNonNullableObject<{ second?: { name?: string | null | undefined; } | null | undefined; }> 409 | testType['first']>(); 410 | // @dts-jest:pass:snap -> _DeepNonNullableObject<{ name?: string | null | undefined; }> 411 | testType['first']['second']>(); 412 | // @dts-jest:pass:snap -> string 413 | testType['first']['second']['name']>(); 414 | 415 | type NestedArrayProps = { 416 | first?: null | { 417 | second?: Array<{ name?: string | null } | undefined | null>; 418 | }; 419 | }; 420 | // @dts-jest:pass:snap -> _DeepNonNullableObject<{ second?: ({ name?: string | null | undefined; } | null | undefined)[] | undefined; }> 421 | testType['first']>(); 422 | // @dts-jest:pass:snap -> _DeepNonNullableArray<{ name?: string | null | undefined; } | null | undefined> 423 | testType['first']['second']>(); 424 | // @dts-jest:pass:snap -> string 425 | testType< 426 | DeepNonNullable['first']['second'][number]['name'] 427 | >(); 428 | 429 | type NestedFunctionProps = { 430 | first?: null | { 431 | second?: (value: number) => string; 432 | }; 433 | }; 434 | // @dts-jest:pass:snap -> _DeepNonNullableObject<{ second?: ((value: number) => string) | undefined; }> 435 | testType['first']>(); 436 | // @dts-jest:pass:snap -> (value: number) => string 437 | testType['first']['second']>(); 438 | // @dts-jest:pass:snap -> string 439 | testType< 440 | ReturnType['first']['second']> 441 | >(); 442 | } 443 | 444 | // @dts-jest:group DeepPartial 445 | { 446 | type NestedProps = { 447 | first: { 448 | second: { 449 | name: string; 450 | }; 451 | }; 452 | }; 453 | const partialNested: DeepPartial = {} as any; 454 | // @dts-jest:pass:snap -> DeepPartial<{ second: { name: string; }; }> | undefined 455 | testType(); 456 | 457 | const second = partialNested.first!.second; 458 | // @dts-jest:pass:snap -> DeepPartial<{ name: string; }> | undefined 459 | testType(); 460 | 461 | const name = second!.name; 462 | // @dts-jest:pass:snap -> string | undefined 463 | testType(); 464 | 465 | type NestedArrayProps = { 466 | first: { 467 | second: Array<{ name: string }>; 468 | }; 469 | }; 470 | 471 | const nestedArrayPartial: DeepPartial = {}; 472 | // @dts-jest:pass:snap -> DeepPartial<{ second: { name: string; }[]; }> | undefined 473 | testType(); 474 | 475 | const arrayProp = nestedArrayPartial.first!.second; 476 | // @dts-jest:pass:snap -> _DeepPartialArray<{ name: string; }> | undefined 477 | testType(); 478 | 479 | const arrayItem = arrayProp![0]; 480 | // @dts-jest:pass:snap -> string | undefined 481 | testType(); 482 | 483 | type NestedFunctionProps = { 484 | first: { 485 | second: (value: number) => string; 486 | }; 487 | }; 488 | const nestedFunctionPartial: DeepPartial = {}; 489 | // @dts-jest:pass:snap -> DeepPartial<{ second: (value: number) => string; }> | undefined 490 | testType(); 491 | 492 | const functionProp = nestedFunctionPartial.first!.second; 493 | // @dts-jest:pass:snap -> ((value: number) => string) | undefined 494 | testType(); 495 | // @dts-jest:pass:snap -> string 496 | testType>>(); 497 | 498 | // @dts-jest:pass:snap -> DeepPartial<{ first: { second: { name: string; }; }; }> 499 | testType>({}); 500 | 501 | () => { 502 | // @dts-jest:pass:snap -> DeepPartial 503 | testType>({}); 504 | }; 505 | } 506 | 507 | // @dts-jest:group Brand 508 | { 509 | // @dts-jest:pass:snap -> Brand 510 | testType>(); 511 | } 512 | 513 | // @dts-jest:group Optional 514 | { 515 | // @dts-jest:pass:snap -> Optional 516 | testType>({}); 517 | // @dts-jest:pass:snap -> Optional 518 | testType>({ age: 99 }); 519 | 520 | // @dts-jest:pass:snap -> Optional 521 | testType>({ name: 'Yolo' }); 522 | // @dts-jest:pass:snap -> Optional 523 | testType>({ name: 'Yolo', age: 99 }); 524 | } 525 | 526 | // @dts-jest:group ValuesType 527 | { 528 | // @dts-jest:pass:snap -> string | number | boolean 529 | testType>(); 530 | 531 | // @dts-jest:pass:snap -> number 532 | testType>(); 533 | // @dts-jest:pass:snap -> symbol 534 | testType>(); 535 | // @dts-jest:pass:snap -> string 536 | testType>>(); 537 | 538 | // @dts-jest:pass:snap -> 1 | 2 539 | testType>(); 540 | // @dts-jest:pass:snap -> 1 | 2 541 | testType>(); 542 | 543 | // @dts-jest:pass:snap -> number 544 | testType>(); 545 | // @dts-jest:pass:snap -> number 546 | testType>(); 547 | // @dts-jest:pass:snap -> number 548 | testType>(); 549 | } 550 | 551 | // @dts-jest:group AugmentedRequired 552 | { 553 | // @dts-jest:pass:snap -> AugmentedRequired, "name" | "age" | "visible"> 554 | testType>>({ 555 | name: 'Yolo', 556 | age: 99, 557 | visible: true, 558 | }); 559 | 560 | // @dts-jest:pass:snap -> AugmentedRequired, "age" | "visible"> 561 | testType, 'age' | 'visible'>>({ 562 | age: 99, 563 | visible: true, 564 | }); 565 | } 566 | 567 | // @dts-jest:group UnionToIntersection 568 | { 569 | // @dts-jest:pass:snap -> { name: string; } & { age: number; } & { visible: boolean; } 570 | testType< 571 | UnionToIntersection< 572 | { name: string } | { age: number } | { visible: boolean } 573 | > 574 | >({ 575 | name: 'Yolo', 576 | age: 99, 577 | visible: true, 578 | }); 579 | 580 | // @dts-jest:pass:snap -> never 581 | testType>(); 582 | 583 | // @dts-jest:pass:snap -> never 584 | testType>(); 585 | 586 | // @dts-jest:pass:snap -> never 587 | testType>(); 588 | } 589 | 590 | // @dts-jest:group Mutable 591 | { 592 | // @dts-jest:pass:snap -> Mutable> 593 | testType>>({ 594 | name: 'Yolo', 595 | age: 99, 596 | visible: true, 597 | }); 598 | 599 | // @dts-jest:pass:snap -> string 600 | testType>['name']>('Yolo'); 601 | 602 | // @dts-jest:pass:snap -> number 603 | testType>['age']>(99); 604 | 605 | // @dts-jest:pass:snap -> boolean 606 | testType>['visible']>(true); 607 | } 608 | -------------------------------------------------------------------------------- /src/mapped-types.spec.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | SetIntersection, 4 | SetDifference, 5 | SetComplement, 6 | SymmetricDifference, 7 | FunctionKeys, 8 | NonUndefined, 9 | NonFunctionKeys, 10 | Omit, 11 | PickByValue, 12 | OmitByValue, 13 | Intersection, 14 | Diff, 15 | Subtract, 16 | Overwrite, 17 | Assign, 18 | Unionize, 19 | PromiseType, 20 | DeepReadonly, 21 | DeepRequired, 22 | DeepNonNullable, 23 | DeepPartial, 24 | MutableKeys, 25 | ReadonlyKeys, 26 | Brand, 27 | _DeepNonNullableArray, 28 | _DeepNonNullableObject, 29 | _DeepReadonlyArray, 30 | _DeepReadonlyObject, 31 | _DeepRequiredArray, 32 | _DeepRequiredObject, 33 | _DeepPartial, 34 | _DeepPartialArray, 35 | RequiredKeys, 36 | OptionalKeys, 37 | UnionKeys, 38 | PickByValueExact, 39 | OmitByValueExact, 40 | Optional, 41 | ValuesType, 42 | AugmentedRequired, 43 | UnionToIntersection, 44 | Mutable, 45 | } from './mapped-types'; 46 | 47 | /** 48 | * Fixtures 49 | */ 50 | 51 | type Props = { name: string; age: number; visible: boolean }; 52 | type DefaultProps = { age: number }; 53 | type NewProps = { age: string; other: string }; 54 | type MixedProps = { 55 | name: string; 56 | setName: (name: string) => void; 57 | someKeys?: string; 58 | someFn?: (...args: any) => any; 59 | }; 60 | type ReadWriteProps = { readonly a: number; b: string }; 61 | type RequiredOptionalProps = { 62 | req: number; 63 | reqUndef: number | undefined; 64 | opt?: string; 65 | optUndef?: string | undefined; 66 | }; 67 | 68 | /** 69 | * Tests 70 | */ 71 | 72 | // @dts-jest:group SetIntersection 73 | { 74 | // @dts-jest:pass:snap 75 | testType>(); 76 | // @dts-jest:pass:snap 77 | testType void), () => void>>(); 78 | } 79 | 80 | // @dts-jest:group SetDifference 81 | { 82 | // @dts-jest:pass:snap 83 | testType>(); 84 | // @dts-jest:pass:snap 85 | testType void), () => void>>(); 86 | } 87 | 88 | // @dts-jest:group SetComplement 89 | { 90 | // @dts-jest:pass:snap 91 | testType>(); 92 | } 93 | 94 | // @dts-jest:group SymmetricDifference 95 | { 96 | // @dts-jest:pass:snap 97 | testType>(); 98 | } 99 | 100 | // @dts-jest:group NonUndefined 101 | { 102 | // @dts-jest:pass:snap 103 | testType>(); 104 | // @dts-jest:pass:snap 105 | testType>(); 106 | } 107 | 108 | // @dts-jest:group FunctionKeys 109 | { 110 | // @dts-jest:pass:snap 111 | testType>(); 112 | } 113 | 114 | // @dts-jest:group NonFunctionKeys 115 | { 116 | // @dts-jest:pass:snap 117 | testType>(); 118 | } 119 | 120 | // @dts-jest:group MutableKeys 121 | { 122 | // @dts-jest:pass:snap 123 | testType>(); 124 | } 125 | 126 | // @dts-jest:group ReadonlyKeys 127 | { 128 | // @dts-jest:pass:snap 129 | testType>(); 130 | } 131 | 132 | // @dts-jest:group RequiredKeys 133 | { 134 | // @dts-jest:pass:snap 135 | testType>(); 136 | } 137 | 138 | // @dts-jest:group OptionalKeys 139 | { 140 | // @dts-jest:pass:snap 141 | testType>(); 142 | } 143 | 144 | // @dts-jest:group UnionKeys 145 | { 146 | // @dts-jest:pass:snap 147 | testType>(); 148 | } 149 | 150 | // @dts-jest:group PickByValue 151 | { 152 | // @dts-jest:pass:snap 153 | testType>(); 154 | // @dts-jest:pass:snap 155 | testType>(); 156 | // @dts-jest:pass:snap 157 | testType>(); 158 | // @dts-jest:pass:snap 159 | testType>(); 160 | 161 | const fn = (props: T) => { 162 | // @dts-jest:pass:snap 163 | testType>(); 164 | }; 165 | } 166 | 167 | // @dts-jest:group PickByValueExact 168 | { 169 | // @dts-jest:pass:snap 170 | testType>(); 171 | // @dts-jest:pass:snap 172 | testType>(); 173 | // @dts-jest:pass:snap 174 | testType>(); 175 | // @dts-jest:pass:snap 176 | testType>(); 177 | 178 | const fn = (props: T) => { 179 | // @dts-jest:pass:snap 180 | testType>(); 181 | }; 182 | } 183 | 184 | // @dts-jest:group Omit 185 | { 186 | // @dts-jest:pass:snap 187 | testType>(); 188 | // @dts-jest:pass:snap 189 | testType>(); 190 | 191 | const fn = (props: T) => { 192 | // @dts-jest:pass:snap 193 | testType>(); 194 | 195 | const { age, ...rest } = props; 196 | // @dts-jest:pass:snap 197 | const result: Omit = rest; 198 | }; 199 | } 200 | 201 | // @dts-jest:group OmitByValue 202 | { 203 | // @dts-jest:pass:snap 204 | testType>(); 205 | // @dts-jest:pass:snap 206 | testType>(); 207 | // @dts-jest:pass:snap 208 | testType>(); 209 | // @dts-jest:pass:snap 210 | testType>(); 211 | 212 | const fn = (props: T) => { 213 | // @dts-jest:pass:snap 214 | testType>(); 215 | }; 216 | } 217 | 218 | // @dts-jest:group OmitByValueExact 219 | { 220 | // @dts-jest:pass:snap 221 | testType>(); 222 | // @dts-jest:pass:snap 223 | testType>(); 224 | // @dts-jest:pass:snap 225 | testType>(); 226 | // @dts-jest:pass:snap 227 | testType>(); 228 | 229 | const fn = (props: T) => { 230 | // @dts-jest:pass:snap 231 | testType>(); 232 | }; 233 | } 234 | 235 | // @dts-jest:group Intersection 236 | { 237 | // @dts-jest:pass:snap 238 | testType>(); 239 | // @dts-jest:pass:snap 240 | testType>(); 241 | 242 | const fn = (props: T) => { 243 | const { age, ...rest } = props; 244 | // @dts-jest:pass:snap 245 | const result: Intersection> = rest; 246 | }; 247 | } 248 | 249 | // @dts-jest:group Diff 250 | { 251 | // @dts-jest:pass:snap 252 | testType>(); 253 | 254 | const fn = (props: T) => { 255 | const { age, ...rest } = props; 256 | // @dts-jest:pass:snap 257 | const result: Diff> = rest; 258 | }; 259 | } 260 | 261 | // @dts-jest:group Subtract 262 | { 263 | // @dts-jest:pass:snap 264 | testType>(); 265 | 266 | const fn = (props: T) => { 267 | const { age, ...rest } = props; 268 | // @dts-jest:pass:snap 269 | const result: Subtract> = rest; 270 | }; 271 | } 272 | 273 | // @dts-jest:group Overwrite 274 | { 275 | // @dts-jest:pass:snap 276 | testType>(); 277 | 278 | const fn = (props: T) => { 279 | const { age, ...rest } = props; 280 | // @dts-jest:pass:snap 281 | const result: Overwrite, T> = rest; 282 | }; 283 | } 284 | 285 | // @dts-jest:group Assign 286 | { 287 | // @dts-jest:pass:snap 288 | testType>(); 289 | 290 | const fn = (props: T) => { 291 | const { age, ...rest } = props; 292 | // @dts-jest:pass:snap 293 | const result: Assign<{}, Omit> = rest; 294 | }; 295 | } 296 | 297 | // @dts-jest:group Unionize 298 | { 299 | // @dts-jest:pass:snap 300 | testType>(); 301 | } 302 | 303 | // @dts-jest:group PromiseType 304 | { 305 | // @dts-jest:pass:snap 306 | testType>>(); 307 | } 308 | 309 | // @dts-jest:group DeepReadonly 310 | { 311 | type NestedProps = { 312 | first: { 313 | second: { 314 | name: string; 315 | }; 316 | }; 317 | }; 318 | // @dts-jest:pass:snap 319 | testType['first']>(); 320 | // @dts-jest:pass:snap 321 | testType['first']['second']>(); 322 | // @dts-jest:pass:snap 323 | testType['first']['second']['name']>(); 324 | 325 | type NestedArrayProps = { 326 | first: { 327 | second: Array<{ name: string }>; 328 | }; 329 | }; 330 | // @dts-jest:pass:snap 331 | testType['first']>(); 332 | // @dts-jest:pass:snap 333 | testType['first']['second']>(); 334 | // @dts-jest:pass:snap 335 | testType['first']['second'][number]['name']>(); 336 | 337 | type NestedFunctionProps = { 338 | first: { 339 | second: (value: number) => string; 340 | }; 341 | }; 342 | // @dts-jest:pass:snap 343 | testType['first']>(); 344 | // @dts-jest:pass:snap 345 | testType['first']['second']>(); 346 | // @dts-jest:pass:snap 347 | testType['first']['second']>>(); 348 | 349 | // @dts-jest:pass:snap 350 | testType>>(); 351 | // @dts-jest:pass:snap 352 | testType>>(); 353 | 354 | // @dts-jest:pass:snap 355 | testType>(); 356 | } 357 | 358 | // @dts-jest:group DeepRequired 359 | { 360 | type NestedProps = { 361 | first?: { 362 | second?: { 363 | name?: string | null; 364 | }; 365 | }; 366 | }; 367 | // @dts-jest:pass:snap 368 | testType['first']>(); 369 | // @dts-jest:pass:snap 370 | testType['first']['second']>(); 371 | // @dts-jest:pass:snap 372 | testType['first']['second']['name']>(); 373 | 374 | type NestedArrayProps = { 375 | first?: { 376 | second?: Array<{ name?: string | null } | undefined>; 377 | }; 378 | }; 379 | // @dts-jest:pass:snap 380 | testType['first']>(); 381 | // @dts-jest:pass:snap 382 | testType['first']['second']>(); 383 | // @dts-jest:pass:snap 384 | testType['first']['second'][number]['name']>(); 385 | 386 | type NestedFunctionProps = { 387 | first?: { 388 | second?: (value: number) => string; 389 | }; 390 | }; 391 | // @dts-jest:pass:snap 392 | testType['first']>(); 393 | // @dts-jest:pass:snap 394 | testType['first']['second']>(); 395 | // @dts-jest:pass:snap 396 | testType['first']['second']>>(); 397 | } 398 | 399 | // @dts-jest:group DeepNonNullable 400 | { 401 | type NestedProps = { 402 | first?: null | { 403 | second?: null | { 404 | name?: null | string; 405 | }; 406 | }; 407 | }; 408 | // @dts-jest:pass:snap 409 | testType['first']>(); 410 | // @dts-jest:pass:snap 411 | testType['first']['second']>(); 412 | // @dts-jest:pass:snap 413 | testType['first']['second']['name']>(); 414 | 415 | type NestedArrayProps = { 416 | first?: null | { 417 | second?: Array<{ name?: string | null } | undefined | null>; 418 | }; 419 | }; 420 | // @dts-jest:pass:snap 421 | testType['first']>(); 422 | // @dts-jest:pass:snap 423 | testType['first']['second']>(); 424 | // @dts-jest:pass:snap 425 | testType< 426 | DeepNonNullable['first']['second'][number]['name'] 427 | >(); 428 | 429 | type NestedFunctionProps = { 430 | first?: null | { 431 | second?: (value: number) => string; 432 | }; 433 | }; 434 | // @dts-jest:pass:snap 435 | testType['first']>(); 436 | // @dts-jest:pass:snap 437 | testType['first']['second']>(); 438 | // @dts-jest:pass:snap 439 | testType< 440 | ReturnType['first']['second']> 441 | >(); 442 | } 443 | 444 | // @dts-jest:group DeepPartial 445 | { 446 | type NestedProps = { 447 | first: { 448 | second: { 449 | name: string; 450 | }; 451 | }; 452 | }; 453 | const partialNested: DeepPartial = {} as any; 454 | // @dts-jest:pass:snap 455 | testType(); 456 | 457 | const second = partialNested.first!.second; 458 | // @dts-jest:pass:snap 459 | testType(); 460 | 461 | const name = second!.name; 462 | // @dts-jest:pass:snap 463 | testType(); 464 | 465 | type NestedArrayProps = { 466 | first: { 467 | second: Array<{ name: string }>; 468 | }; 469 | }; 470 | 471 | const nestedArrayPartial: DeepPartial = {}; 472 | // @dts-jest:pass:snap 473 | testType(); 474 | 475 | const arrayProp = nestedArrayPartial.first!.second; 476 | // @dts-jest:pass:snap 477 | testType(); 478 | 479 | const arrayItem = arrayProp![0]; 480 | // @dts-jest:pass:snap 481 | testType(); 482 | 483 | type NestedFunctionProps = { 484 | first: { 485 | second: (value: number) => string; 486 | }; 487 | }; 488 | const nestedFunctionPartial: DeepPartial = {}; 489 | // @dts-jest:pass:snap 490 | testType(); 491 | 492 | const functionProp = nestedFunctionPartial.first!.second; 493 | // @dts-jest:pass:snap 494 | testType(); 495 | // @dts-jest:pass:snap 496 | testType>>(); 497 | 498 | // @dts-jest:pass:snap 499 | testType>({}); 500 | 501 | () => { 502 | // @dts-jest:pass:snap 503 | testType>({}); 504 | }; 505 | } 506 | 507 | // @dts-jest:group Brand 508 | { 509 | // @dts-jest:pass:snap 510 | testType>(); 511 | } 512 | 513 | // @dts-jest:group Optional 514 | { 515 | // @dts-jest:pass:snap 516 | testType>({}); 517 | // @dts-jest:pass:snap 518 | testType>({ age: 99 }); 519 | 520 | // @dts-jest:pass:snap 521 | testType>({ name: 'Yolo' }); 522 | // @dts-jest:pass:snap 523 | testType>({ name: 'Yolo', age: 99 }); 524 | } 525 | 526 | // @dts-jest:group ValuesType 527 | { 528 | // @dts-jest:pass:snap 529 | testType>(); 530 | 531 | // @dts-jest:pass:snap 532 | testType>(); 533 | // @dts-jest:pass:snap 534 | testType>(); 535 | // @dts-jest:pass:snap 536 | testType>>(); 537 | 538 | // @dts-jest:pass:snap 539 | testType>(); 540 | // @dts-jest:pass:snap 541 | testType>(); 542 | 543 | // @dts-jest:pass:snap 544 | testType>(); 545 | // @dts-jest:pass:snap 546 | testType>(); 547 | // @dts-jest:pass:snap 548 | testType>(); 549 | } 550 | 551 | // @dts-jest:group AugmentedRequired 552 | { 553 | // @dts-jest:pass:snap 554 | testType>>({ 555 | name: 'Yolo', 556 | age: 99, 557 | visible: true, 558 | }); 559 | 560 | // @dts-jest:pass:snap 561 | testType, 'age' | 'visible'>>({ 562 | age: 99, 563 | visible: true, 564 | }); 565 | } 566 | 567 | // @dts-jest:group UnionToIntersection 568 | { 569 | // @dts-jest:pass:snap 570 | testType< 571 | UnionToIntersection< 572 | { name: string } | { age: number } | { visible: boolean } 573 | > 574 | >({ 575 | name: 'Yolo', 576 | age: 99, 577 | visible: true, 578 | }); 579 | 580 | // @dts-jest:pass:snap 581 | testType>(); 582 | 583 | // @dts-jest:pass:snap 584 | testType>(); 585 | 586 | // @dts-jest:pass:snap 587 | testType>(); 588 | } 589 | 590 | // @dts-jest:group Mutable 591 | { 592 | // @dts-jest:pass:snap 593 | testType>>({ 594 | name: 'Yolo', 595 | age: 99, 596 | visible: true, 597 | }); 598 | 599 | // @dts-jest:pass:snap 600 | testType>['name']>('Yolo'); 601 | 602 | // @dts-jest:pass:snap 603 | testType>['age']>(99); 604 | 605 | // @dts-jest:pass:snap 606 | testType>['visible']>(true); 607 | } 608 | -------------------------------------------------------------------------------- /src/mapped-types.ts: -------------------------------------------------------------------------------- 1 | import { Primitive } from './aliases-and-guards'; 2 | 3 | /** 4 | * Credits to all the people who given inspiration and shared some very useful code snippets 5 | * in the following github issue: https://github.com/Microsoft/TypeScript/issues/12215 6 | */ 7 | 8 | /** 9 | * SetIntersection (same as Extract) 10 | * @desc Set intersection of given union types `A` and `B` 11 | * @example 12 | * // Expect: "2" | "3" 13 | * SetIntersection<'1' | '2' | '3', '2' | '3' | '4'>; 14 | * 15 | * // Expect: () => void 16 | * SetIntersection void), Function>; 17 | */ 18 | export type SetIntersection = A extends B ? A : never; 19 | 20 | /** 21 | * SetDifference (same as Exclude) 22 | * @desc Set difference of given union types `A` and `B` 23 | * @example 24 | * // Expect: "1" 25 | * SetDifference<'1' | '2' | '3', '2' | '3' | '4'>; 26 | * 27 | * // Expect: string | number 28 | * SetDifference void), Function>; 29 | */ 30 | export type SetDifference = A extends B ? never : A; 31 | 32 | /** 33 | * SetComplement 34 | * @desc Set complement of given union types `A` and (it's subset) `A1` 35 | * @example 36 | * // Expect: "1" 37 | * SetComplement<'1' | '2' | '3', '2' | '3'>; 38 | */ 39 | export type SetComplement = SetDifference; 40 | 41 | /** 42 | * SymmetricDifference 43 | * @desc Set difference of union and intersection of given union types `A` and `B` 44 | * @example 45 | * // Expect: "1" | "4" 46 | * SymmetricDifference<'1' | '2' | '3', '2' | '3' | '4'>; 47 | */ 48 | export type SymmetricDifference = SetDifference; 49 | 50 | /** 51 | * NonUndefined 52 | * @desc Exclude undefined from set `A` 53 | * @example 54 | * // Expect: "string | null" 55 | * SymmetricDifference; 56 | */ 57 | export type NonUndefined = A extends undefined ? never : A; 58 | 59 | /** 60 | * NonNullable 61 | * @desc Exclude undefined and null from set `A` 62 | * @example 63 | * // Expect: "string" 64 | * SymmetricDifference; 65 | */ 66 | // type NonNullable - built-in 67 | 68 | /** 69 | * FunctionKeys 70 | * @desc Get union type of keys that are functions in object type `T` 71 | * @example 72 | * type MixedProps = {name: string; setName: (name: string) => void; someKeys?: string; someFn?: (...args: any) => any;}; 73 | * 74 | * // Expect: "setName | someFn" 75 | * type Keys = FunctionKeys; 76 | */ 77 | export type FunctionKeys = { 78 | [K in keyof T]-?: NonUndefined extends Function ? K : never; 79 | }[keyof T]; 80 | 81 | /** 82 | * NonFunctionKeys 83 | * @desc Get union type of keys that are non-functions in object type `T` 84 | * @example 85 | * type MixedProps = {name: string; setName: (name: string) => void; someKeys?: string; someFn?: (...args: any) => any;}; 86 | * 87 | * // Expect: "name | someKey" 88 | * type Keys = NonFunctionKeys; 89 | */ 90 | export type NonFunctionKeys = { 91 | [K in keyof T]-?: NonUndefined extends Function ? never : K; 92 | }[keyof T]; 93 | 94 | /** 95 | * MutableKeys 96 | * @desc Get union type of keys that are mutable in object type `T` 97 | * Credit: Matt McCutchen 98 | * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript 99 | * @example 100 | * type Props = { readonly foo: string; bar: number }; 101 | * 102 | * // Expect: "bar" 103 | * type Keys = MutableKeys; 104 | */ 105 | export type MutableKeys = { 106 | [P in keyof T]-?: IfEquals< 107 | { [Q in P]: T[P] }, 108 | { -readonly [Q in P]: T[P] }, 109 | P 110 | >; 111 | }[keyof T]; 112 | export type WritableKeys = MutableKeys; 113 | 114 | /** 115 | * ReadonlyKeys 116 | * @desc Get union type of keys that are readonly in object type `T` 117 | * Credit: Matt McCutchen 118 | * https://stackoverflow.com/questions/52443276/how-to-exclude-getter-only-properties-from-type-in-typescript 119 | * @example 120 | * type Props = { readonly foo: string; bar: number }; 121 | * 122 | * // Expect: "foo" 123 | * type Keys = ReadonlyKeys; 124 | */ 125 | export type ReadonlyKeys = { 126 | [P in keyof T]-?: IfEquals< 127 | { [Q in P]: T[P] }, 128 | { -readonly [Q in P]: T[P] }, 129 | never, 130 | P 131 | >; 132 | }[keyof T]; 133 | 134 | type IfEquals = (() => T extends X ? 1 : 2) extends < 135 | T 136 | >() => T extends Y ? 1 : 2 137 | ? A 138 | : B; 139 | 140 | /** 141 | * RequiredKeys 142 | * @desc Get union type of keys that are required in object type `T` 143 | * @see https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object 144 | * @example 145 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; 146 | * 147 | * // Expect: "req" | "reqUndef" 148 | * type Keys = RequiredKeys; 149 | */ 150 | export type RequiredKeys = { 151 | [K in keyof T]-?: {} extends Pick ? never : K; 152 | }[keyof T]; 153 | 154 | /** 155 | * OptionalKeys 156 | * @desc Get union type of keys that are optional in object type `T` 157 | * @see https://stackoverflow.com/questions/52984808/is-there-a-way-to-get-all-required-properties-of-a-typescript-object 158 | * @example 159 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; }; 160 | * 161 | * // Expect: "opt" | "optUndef" 162 | * type Keys = OptionalKeys; 163 | */ 164 | export type OptionalKeys = { 165 | [K in keyof T]-?: {} extends Pick ? K : never; 166 | }[keyof T]; 167 | 168 | /** 169 | * UnionKeys 170 | * @desc Get keys of all objects in the union type `U` 171 | * Credit: filipomar 172 | * @see https://github.com/piotrwitek/utility-types/issues/192 173 | * @example 174 | * // Expect: 'name' | 'age' | 'visible' 175 | * UnionKeys<{ name: string; age: string } | { age: number } | { visible: boolean }> 176 | */ 177 | export type UnionKeys = keyof UnionToIntersection>; 178 | 179 | /** 180 | * Pick (complements Omit) 181 | * @desc From `T` pick a set of properties by key `K` 182 | * @example 183 | * type Props = { name: string; age: number; visible: boolean }; 184 | * 185 | * // Expect: { age: number; } 186 | * type Props = Pick; 187 | */ 188 | namespace Pick {} 189 | 190 | /** 191 | * PickByValue 192 | * @desc From `T` pick a set of properties by value matching `ValueType`. 193 | * Credit: [Piotr Lewandowski](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c) 194 | * @example 195 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 196 | * 197 | * // Expect: { req: number } 198 | * type Props = PickByValue; 199 | * // Expect: { req: number; reqUndef: number | undefined; } 200 | * type Props = PickByValue; 201 | */ 202 | export type PickByValue = Pick< 203 | T, 204 | { [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T] 205 | >; 206 | 207 | /** 208 | * PickByValueExact 209 | * @desc From `T` pick a set of properties by value matching exact `ValueType`. 210 | * @example 211 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 212 | * 213 | * // Expect: { req: number } 214 | * type Props = PickByValueExact; 215 | * // Expect: { reqUndef: number | undefined; } 216 | * type Props = PickByValueExact; 217 | */ 218 | export type PickByValueExact = Pick< 219 | T, 220 | { 221 | [Key in keyof T]-?: [ValueType] extends [T[Key]] 222 | ? [T[Key]] extends [ValueType] 223 | ? Key 224 | : never 225 | : never; 226 | }[keyof T] 227 | >; 228 | 229 | /** 230 | * Omit (complements Pick) 231 | * @desc From `T` remove a set of properties by key `K` 232 | * @example 233 | * type Props = { name: string; age: number; visible: boolean }; 234 | * 235 | * // Expect: { name: string; visible: boolean; } 236 | * type Props = Omit; 237 | */ 238 | export type Omit = Pick>; 239 | 240 | /** 241 | * OmitByValue 242 | * @desc From `T` remove a set of properties by value matching `ValueType`. 243 | * Credit: [Piotr Lewandowski](https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c) 244 | * @example 245 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 246 | * 247 | * // Expect: { reqUndef: number | undefined; opt?: string; } 248 | * type Props = OmitByValue; 249 | * // Expect: { opt?: string; } 250 | * type Props = OmitByValue; 251 | */ 252 | export type OmitByValue = Pick< 253 | T, 254 | { [Key in keyof T]-?: T[Key] extends ValueType ? never : Key }[keyof T] 255 | >; 256 | 257 | /** 258 | * OmitByValueExact 259 | * @desc From `T` remove a set of properties by value matching exact `ValueType`. 260 | * @example 261 | * type Props = { req: number; reqUndef: number | undefined; opt?: string; }; 262 | * 263 | * // Expect: { reqUndef: number | undefined; opt?: string; } 264 | * type Props = OmitByValueExact; 265 | * // Expect: { req: number; opt?: string } 266 | * type Props = OmitByValueExact; 267 | */ 268 | export type OmitByValueExact = Pick< 269 | T, 270 | { 271 | [Key in keyof T]-?: [ValueType] extends [T[Key]] 272 | ? [T[Key]] extends [ValueType] 273 | ? never 274 | : Key 275 | : Key; 276 | }[keyof T] 277 | >; 278 | 279 | /** 280 | * Intersection 281 | * @desc From `T` pick properties that exist in `U` 282 | * @example 283 | * type Props = { name: string; age: number; visible: boolean }; 284 | * type DefaultProps = { age: number }; 285 | * 286 | * // Expect: { age: number; } 287 | * type DuplicateProps = Intersection; 288 | */ 289 | export type Intersection = Pick< 290 | T, 291 | Extract & Extract 292 | >; 293 | 294 | /** 295 | * Diff 296 | * @desc From `T` remove properties that exist in `U` 297 | * @example 298 | * type Props = { name: string; age: number; visible: boolean }; 299 | * type DefaultProps = { age: number }; 300 | * 301 | * // Expect: { name: string; visible: boolean; } 302 | * type DiffProps = Diff; 303 | */ 304 | export type Diff = Pick< 305 | T, 306 | SetDifference 307 | >; 308 | 309 | /** 310 | * Subtract 311 | * @desc From `T` remove properties that exist in `T1` (`T1` has a subset of the properties of `T`) 312 | * @example 313 | * type Props = { name: string; age: number; visible: boolean }; 314 | * type DefaultProps = { age: number }; 315 | * 316 | * // Expect: { name: string; visible: boolean; } 317 | * type RestProps = Subtract; 318 | */ 319 | export type Subtract = Pick< 320 | T, 321 | SetComplement 322 | >; 323 | 324 | /** 325 | * Overwrite 326 | * @desc From `U` overwrite properties to `T` 327 | * @example 328 | * type Props = { name: string; age: number; visible: boolean }; 329 | * type NewProps = { age: string; other: string }; 330 | * 331 | * // Expect: { name: string; age: string; visible: boolean; } 332 | * type ReplacedProps = Overwrite; 333 | */ 334 | export type Overwrite< 335 | T extends object, 336 | U extends object, 337 | I = Diff & Intersection 338 | > = Pick; 339 | 340 | /** 341 | * Assign 342 | * @desc From `U` assign properties to `T` (just like object assign) 343 | * @example 344 | * type Props = { name: string; age: number; visible: boolean }; 345 | * type NewProps = { age: string; other: string }; 346 | * 347 | * // Expect: { name: string; age: number; visible: boolean; other: string; } 348 | * type ExtendedProps = Assign; 349 | */ 350 | export type Assign< 351 | T extends object, 352 | U extends object, 353 | I = Diff & Intersection & Diff 354 | > = Pick; 355 | 356 | /** 357 | * Exact 358 | * @desc Create branded object type for exact type matching 359 | */ 360 | export type Exact = A & { __brand: keyof A }; 361 | 362 | /** 363 | * Unionize 364 | * @desc Disjoin object to form union of objects, each with single property 365 | * @example 366 | * type Props = { name: string; age: number; visible: boolean }; 367 | * 368 | * // Expect: { name: string; } | { age: number; } | { visible: boolean; } 369 | * type UnionizedType = Unionize; 370 | */ 371 | export type Unionize = { 372 | [P in keyof T]: { [Q in P]: T[P] }; 373 | }[keyof T]; 374 | 375 | /** 376 | * PromiseType 377 | * @desc Obtain Promise resolve type 378 | * @example 379 | * // Expect: string; 380 | * type Response = PromiseType>; 381 | */ 382 | export type PromiseType> = T extends Promise 383 | ? U 384 | : never; 385 | 386 | // TODO: inline _DeepReadonlyArray with infer in DeepReadonly, same for all other deep types 387 | /** 388 | * DeepReadonly 389 | * @desc Readonly that works for deeply nested structure 390 | * @example 391 | * // Expect: { 392 | * // readonly first: { 393 | * // readonly second: { 394 | * // readonly name: string; 395 | * // }; 396 | * // }; 397 | * // } 398 | * type NestedProps = { 399 | * first: { 400 | * second: { 401 | * name: string; 402 | * }; 403 | * }; 404 | * }; 405 | * type ReadonlyNestedProps = DeepReadonly; 406 | */ 407 | export type DeepReadonly = T extends ((...args: any[]) => any) | Primitive 408 | ? T 409 | : T extends _DeepReadonlyArray 410 | ? _DeepReadonlyArray 411 | : T extends _DeepReadonlyObject 412 | ? _DeepReadonlyObject 413 | : T; 414 | /** @private */ 415 | // tslint:disable-next-line:class-name 416 | export interface _DeepReadonlyArray extends ReadonlyArray> {} 417 | /** @private */ 418 | export type _DeepReadonlyObject = { 419 | readonly [P in keyof T]: DeepReadonly; 420 | }; 421 | 422 | /** 423 | * DeepRequired 424 | * @desc Required that works for deeply nested structure 425 | * @example 426 | * // Expect: { 427 | * // first: { 428 | * // second: { 429 | * // name: string; 430 | * // }; 431 | * // }; 432 | * // } 433 | * type NestedProps = { 434 | * first?: { 435 | * second?: { 436 | * name?: string; 437 | * }; 438 | * }; 439 | * }; 440 | * type RequiredNestedProps = DeepRequired; 441 | */ 442 | export type DeepRequired = T extends (...args: any[]) => any 443 | ? T 444 | : T extends any[] 445 | ? _DeepRequiredArray 446 | : T extends object 447 | ? _DeepRequiredObject 448 | : T; 449 | /** @private */ 450 | // tslint:disable-next-line:class-name 451 | export interface _DeepRequiredArray 452 | extends Array>> {} 453 | /** @private */ 454 | export type _DeepRequiredObject = { 455 | [P in keyof T]-?: DeepRequired>; 456 | }; 457 | 458 | /** 459 | * DeepNonNullable 460 | * @desc NonNullable that works for deeply nested structure 461 | * @example 462 | * // Expect: { 463 | * // first: { 464 | * // second: { 465 | * // name: string; 466 | * // }; 467 | * // }; 468 | * // } 469 | * type NestedProps = { 470 | * first?: null | { 471 | * second?: null | { 472 | * name?: string | null | 473 | * undefined; 474 | * }; 475 | * }; 476 | * }; 477 | * type RequiredNestedProps = DeepNonNullable; 478 | */ 479 | export type DeepNonNullable = T extends (...args: any[]) => any 480 | ? T 481 | : T extends any[] 482 | ? _DeepNonNullableArray 483 | : T extends object 484 | ? _DeepNonNullableObject 485 | : T; 486 | /** @private */ 487 | // tslint:disable-next-line:class-name 488 | export interface _DeepNonNullableArray 489 | extends Array>> {} 490 | /** @private */ 491 | export type _DeepNonNullableObject = { 492 | [P in keyof T]-?: DeepNonNullable>; 493 | }; 494 | 495 | /** 496 | * DeepPartial 497 | * @desc Partial that works for deeply nested structure 498 | * @example 499 | * // Expect: { 500 | * // first?: { 501 | * // second?: { 502 | * // name?: string; 503 | * // }; 504 | * // }; 505 | * // } 506 | * type NestedProps = { 507 | * first: { 508 | * second: { 509 | * name: string; 510 | * }; 511 | * }; 512 | * }; 513 | * type PartialNestedProps = DeepPartial; 514 | */ 515 | export type DeepPartial = { [P in keyof T]?: _DeepPartial }; 516 | 517 | /** @private */ 518 | export type _DeepPartial = T extends Function 519 | ? T 520 | : T extends Array 521 | ? _DeepPartialArray 522 | : T extends object 523 | ? DeepPartial 524 | : T | undefined; 525 | /** @private */ 526 | // tslint:disable-next-line:class-name 527 | export interface _DeepPartialArray extends Array<_DeepPartial> {} 528 | 529 | /** 530 | * Brand 531 | * @desc Define nominal type of U based on type of T. Similar to Opaque types in Flow. 532 | * @example 533 | * type USD = Brand 534 | * type EUR = Brand 535 | * 536 | * const tax = 5 as USD; 537 | * const usd = 10 as USD; 538 | * const eur = 10 as EUR; 539 | * 540 | * function gross(net: USD): USD { 541 | * return (net + tax) as USD; 542 | * } 543 | * 544 | * // Expect: No compile error 545 | * gross(usd); 546 | * // Expect: Compile error (Type '"EUR"' is not assignable to type '"USD"'.) 547 | * gross(eur); 548 | */ 549 | export type Brand = T & { __brand: U }; 550 | 551 | /** 552 | * Optional 553 | * @desc From `T` make a set of properties by key `K` become optional 554 | * @example 555 | * type Props = { 556 | * name: string; 557 | * age: number; 558 | * visible: boolean; 559 | * }; 560 | * 561 | * // Expect: { name?: string; age?: number; visible?: boolean; } 562 | * type Props = Optional; 563 | * 564 | * // Expect: { name: string; age?: number; visible?: boolean; } 565 | * type Props = Optional; 566 | */ 567 | export type Optional = Omit< 568 | T, 569 | K 570 | > & 571 | Partial>; 572 | 573 | /** 574 | * ValuesType 575 | * @desc Get the union type of all the values in an object, array or array-like type `T` 576 | * @example 577 | * type Props = { name: string; age: number; visible: boolean }; 578 | * // Expect: string | number | boolean 579 | * type PropsValues = ValuesType; 580 | * 581 | * type NumberArray = number[]; 582 | * // Expect: number 583 | * type NumberItems = ValuesType; 584 | * 585 | * type ReadonlySymbolArray = readonly symbol[]; 586 | * // Expect: symbol 587 | * type SymbolItems = ValuesType; 588 | * 589 | * type NumberTuple = [1, 2]; 590 | * // Expect: 1 | 2 591 | * type NumberUnion = ValuesType; 592 | * 593 | * type ReadonlyNumberTuple = readonly [1, 2]; 594 | * // Expect: 1 | 2 595 | * type AnotherNumberUnion = ValuesType; 596 | * 597 | * type BinaryArray = Uint8Array; 598 | * // Expect: number 599 | * type BinaryItems = ValuesType; 600 | */ 601 | export type ValuesType< 602 | T extends ReadonlyArray | ArrayLike | Record 603 | > = T extends ReadonlyArray 604 | ? T[number] 605 | : T extends ArrayLike 606 | ? T[number] 607 | : T extends object 608 | ? T[keyof T] 609 | : never; 610 | 611 | /** 612 | * Required 613 | * @desc From `T` make a set of properties by key `K` become required 614 | * @example 615 | * type Props = { 616 | * name?: string; 617 | * age?: number; 618 | * visible?: boolean; 619 | * }; 620 | * 621 | * // Expect: { name: string; age: number; visible: boolean; } 622 | * type Props = Required; 623 | * 624 | * // Expect: { name?: string; age: number; visible: boolean; } 625 | * type Props = Required; 626 | */ 627 | export type AugmentedRequired< 628 | T extends object, 629 | K extends keyof T = keyof T 630 | > = Omit & Required>; 631 | 632 | /** 633 | * UnionToIntersection 634 | * @desc Get intersection type given union type `U` 635 | * Credit: jcalz 636 | * @see https://stackoverflow.com/a/50375286/7381355 637 | * @example 638 | * // Expect: { name: string } & { age: number } & { visible: boolean } 639 | * UnionToIntersection<{ name: string } | { age: number } | { visible: boolean }> 640 | */ 641 | export type UnionToIntersection = (U extends any 642 | ? (k: U) => void 643 | : never) extends (k: infer I) => void 644 | ? I 645 | : never; 646 | 647 | /** 648 | * Mutable 649 | * @desc From `T` make all properties become mutable 650 | * @example 651 | * type Props = { 652 | * readonly name: string; 653 | * readonly age: number; 654 | * readonly visible: boolean; 655 | * }; 656 | * 657 | * // Expect: { name: string; age: number; visible: boolean; } 658 | * Mutable; 659 | */ 660 | export type Mutable = { -readonly [P in keyof T]: T[P] }; 661 | export type Writable = Mutable; 662 | -------------------------------------------------------------------------------- /src/utility-types.spec.snap.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | $Call, 4 | $Keys, 5 | $Values, 6 | $ReadOnly, 7 | $Diff, 8 | $PropertyType, 9 | $ElementType, 10 | $Shape, 11 | $NonMaybeType, 12 | Class, 13 | mixed, 14 | } from './utility-types'; 15 | import { _DeepReadonlyObject } from './mapped-types'; 16 | /** 17 | * Fixtures 18 | */ 19 | 20 | type Props = { name: string; age: number; visible: boolean }; 21 | type DefaultProps = { age: number }; 22 | 23 | class Foo {} 24 | 25 | /** 26 | * Tests 27 | */ 28 | 29 | // @dts-jest:group $Keys 30 | { 31 | // @dts-jest:pass:snap -> "name" | "age" | "visible" 32 | testType<$Keys>(); 33 | } 34 | 35 | // @dts-jest:group $Values 36 | { 37 | // @dts-jest:pass:snap -> string | number | boolean 38 | testType<$Values>(); 39 | } 40 | 41 | // @dts-jest:group $ReadOnly 42 | { 43 | // @dts-jest:pass:snap -> _DeepReadonlyObject<{ name: string; age: number; visible: boolean; }> 44 | testType<$ReadOnly>(); 45 | } 46 | 47 | // @dts-jest:group $Diff 48 | { 49 | // @dts-jest:pass:snap -> Pick 50 | testType<$Diff>(); 51 | } 52 | 53 | // @dts-jest:group $PropertyType 54 | { 55 | // @dts-jest:pass:snap -> string 56 | testType<$PropertyType>(); 57 | 58 | // @dts-jest:pass:snap -> boolean 59 | testType<$PropertyType<[boolean, number], '0'>>(); 60 | // @dts-jest:pass:snap -> number 61 | testType<$PropertyType<[boolean, number], '1'>>(); 62 | } 63 | 64 | // @dts-jest:group $ElementType 65 | { 66 | // @dts-jest:pass:snap -> string 67 | testType<$ElementType>(); 68 | 69 | // @dts-jest:pass:snap -> boolean 70 | testType<$ElementType<[boolean, number], 0>>(); 71 | // @dts-jest:pass:snap -> number 72 | testType<$ElementType<[boolean, number], 1>>(); 73 | 74 | // @dts-jest:pass:snap -> boolean 75 | testType<$ElementType>(); 76 | 77 | // @dts-jest:pass:snap -> number 78 | testType<$ElementType<{ [key: string]: number }, string>>(); 79 | } 80 | 81 | // @dts-jest:group $Call 82 | { 83 | // @dts-jest:pass:snap -> { type: "ADD"; payload: number; } 84 | testType<$Call<(amount: number) => { type: 'ADD'; payload: number }>>(); 85 | 86 | type ExtractPropType = (arg: T) => T['prop']; 87 | type Obj = { prop: number }; 88 | type PropType = $Call>; 89 | // @dts-jest:pass:snap -> number 90 | testType(); 91 | // type Nope = $Call>; // Error: argument doesn't match `Obj`. 92 | 93 | type ExtractReturnType any> = (arg: T) => ReturnType; 94 | type Fn = () => number; 95 | type FnReturnType = $Call>; 96 | // @dts-jest:pass:snap -> number 97 | testType(); 98 | } 99 | 100 | // @dts-jest:group $Shape 101 | { 102 | // @dts-jest:pass:snap -> Partial 103 | testType<$Shape>(); 104 | } 105 | 106 | // @dts-jest:group $NonMaybeType 107 | { 108 | // @dts-jest:pass:snap -> string 109 | testType<$NonMaybeType>(); 110 | } 111 | 112 | // @dts-jest:group Class 113 | { 114 | // @dts-jest:pass:snap -> Class 115 | testType>(); 116 | } 117 | 118 | // @dts-jest:group mixed 119 | { 120 | // @dts-jest:pass:snap -> unknown 121 | testType(); 122 | } 123 | -------------------------------------------------------------------------------- /src/utility-types.spec.ts: -------------------------------------------------------------------------------- 1 | import { testType } from '../utils/test-utils'; 2 | import { 3 | $Call, 4 | $Keys, 5 | $Values, 6 | $ReadOnly, 7 | $Diff, 8 | $PropertyType, 9 | $ElementType, 10 | $Shape, 11 | $NonMaybeType, 12 | Class, 13 | mixed, 14 | } from './utility-types'; 15 | import { _DeepReadonlyObject } from './mapped-types'; 16 | /** 17 | * Fixtures 18 | */ 19 | 20 | type Props = { name: string; age: number; visible: boolean }; 21 | type DefaultProps = { age: number }; 22 | 23 | class Foo {} 24 | 25 | /** 26 | * Tests 27 | */ 28 | 29 | // @dts-jest:group $Keys 30 | { 31 | // @dts-jest:pass:snap 32 | testType<$Keys>(); 33 | } 34 | 35 | // @dts-jest:group $Values 36 | { 37 | // @dts-jest:pass:snap 38 | testType<$Values>(); 39 | } 40 | 41 | // @dts-jest:group $ReadOnly 42 | { 43 | // @dts-jest:pass:snap 44 | testType<$ReadOnly>(); 45 | } 46 | 47 | // @dts-jest:group $Diff 48 | { 49 | // @dts-jest:pass:snap 50 | testType<$Diff>(); 51 | } 52 | 53 | // @dts-jest:group $PropertyType 54 | { 55 | // @dts-jest:pass:snap 56 | testType<$PropertyType>(); 57 | 58 | // @dts-jest:pass:snap 59 | testType<$PropertyType<[boolean, number], '0'>>(); 60 | // @dts-jest:pass:snap 61 | testType<$PropertyType<[boolean, number], '1'>>(); 62 | } 63 | 64 | // @dts-jest:group $ElementType 65 | { 66 | // @dts-jest:pass:snap 67 | testType<$ElementType>(); 68 | 69 | // @dts-jest:pass:snap 70 | testType<$ElementType<[boolean, number], 0>>(); 71 | // @dts-jest:pass:snap 72 | testType<$ElementType<[boolean, number], 1>>(); 73 | 74 | // @dts-jest:pass:snap 75 | testType<$ElementType>(); 76 | 77 | // @dts-jest:pass:snap 78 | testType<$ElementType<{ [key: string]: number }, string>>(); 79 | } 80 | 81 | // @dts-jest:group $Call 82 | { 83 | // @dts-jest:pass:snap 84 | testType<$Call<(amount: number) => { type: 'ADD'; payload: number }>>(); 85 | 86 | type ExtractPropType = (arg: T) => T['prop']; 87 | type Obj = { prop: number }; 88 | type PropType = $Call>; 89 | // @dts-jest:pass:snap 90 | testType(); 91 | // type Nope = $Call>; // Error: argument doesn't match `Obj`. 92 | 93 | type ExtractReturnType any> = (arg: T) => ReturnType; 94 | type Fn = () => number; 95 | type FnReturnType = $Call>; 96 | // @dts-jest:pass:snap 97 | testType(); 98 | } 99 | 100 | // @dts-jest:group $Shape 101 | { 102 | // @dts-jest:pass:snap 103 | testType<$Shape>(); 104 | } 105 | 106 | // @dts-jest:group $NonMaybeType 107 | { 108 | // @dts-jest:pass:snap 109 | testType<$NonMaybeType>(); 110 | } 111 | 112 | // @dts-jest:group Class 113 | { 114 | // @dts-jest:pass:snap 115 | testType>(); 116 | } 117 | 118 | // @dts-jest:group mixed 119 | { 120 | // @dts-jest:pass:snap 121 | testType(); 122 | } 123 | -------------------------------------------------------------------------------- /src/utility-types.ts: -------------------------------------------------------------------------------- 1 | import { SetComplement, DeepReadonly } from './mapped-types'; 2 | 3 | /** 4 | * $Keys 5 | * @desc Get the union type of all the keys in an object type `T` 6 | * @see https://flow.org/en/docs/types/utilities/#toc-keys 7 | * @example 8 | * type Props = { name: string; age: number; visible: boolean }; 9 | * 10 | * // Expect: "name" | "age" | "visible" 11 | * type PropsKeys = $Keys; 12 | */ 13 | export type $Keys = keyof T; 14 | 15 | /** 16 | * $Values 17 | * @desc Get the union type of all the values in an object type `T` 18 | * @see https://flow.org/en/docs/types/utilities/#toc-values 19 | * @example 20 | * type Props = { name: string; age: number; visible: boolean }; 21 | * 22 | * // Expect: string | number | boolean 23 | * type PropsValues = $Values; 24 | */ 25 | export type $Values = T[keyof T]; 26 | 27 | /** 28 | * $ReadOnly 29 | * @desc Get the read-only version of a given object type `T` (it works on nested data structure) 30 | * @see https://flow.org/en/docs/types/utilities/#toc-readonly 31 | * @example 32 | * type Props = { name: string; age: number; visible: boolean }; 33 | * 34 | * // Expect: Readonly<{ name: string; age: number; visible: boolean; }> 35 | * type ReadOnlyProps = $ReadOnly; 36 | */ 37 | export type $ReadOnly = DeepReadonly; 38 | 39 | /** 40 | * $Diff 41 | * @desc Get the set difference of a given object types `T` and `U` (`T \ U`) 42 | * @see https://flow.org/en/docs/types/utilities/#toc-diff 43 | * @example 44 | * type Props = { name: string; age: number; visible: boolean }; 45 | * type DefaultProps = { age: number }; 46 | * 47 | * // Expect: { name: string; visible: boolean; } 48 | * type RequiredProps = Diff; 49 | */ 50 | export type $Diff = Pick< 51 | T, 52 | SetComplement 53 | >; 54 | 55 | /** 56 | * $PropertyType 57 | * @desc Get the type of property of an object at a given key `K` 58 | * @see https://flow.org/en/docs/types/utilities/#toc-propertytype 59 | * @example 60 | * // Expect: string; 61 | * type Props = { name: string; age: number; visible: boolean }; 62 | * type NameType = $PropertyType; 63 | * 64 | * // Expect: boolean 65 | * type Tuple = [boolean, number]; 66 | * type A = $PropertyType; 67 | * // Expect: number 68 | * type B = $PropertyType; 69 | */ 70 | export type $PropertyType = T[K]; 71 | 72 | /** 73 | * $ElementType 74 | * @desc Get the type of elements inside of array, tuple or object of type `T`, that matches the given index type `K` 75 | * @see https://flow.org/en/docs/types/utilities/#toc-elementtype 76 | * @example 77 | * // Expect: string; 78 | * type Props = { name: string; age: number; visible: boolean }; 79 | * type NameType = $ElementType; 80 | * 81 | * // Expect: boolean 82 | * type Tuple = [boolean, number]; 83 | * type A = $ElementType; 84 | * // Expect: number 85 | * type B = $ElementType; 86 | * 87 | * // Expect: boolean 88 | * type Arr = boolean[]; 89 | * type ItemsType = $ElementType; 90 | * 91 | * // Expect: number 92 | * type Obj = { [key: string]: number }; 93 | * type ValuesType = $ElementType; 94 | */ 95 | export type $ElementType< 96 | T extends { [P in K & any]: any }, 97 | K extends keyof T | number 98 | > = T[K]; 99 | 100 | /** 101 | * $Call 102 | * @desc Get the return type from a given typeof expression 103 | * @see https://flow.org/en/docs/types/utilities/#toc-call 104 | * @example 105 | * // Common use-case 106 | * const add = (amount: number) => ({ type: 'ADD' as 'ADD', payload: amount }); 107 | * type AddAction = $Call; // { type: 'ADD'; payload: number } 108 | * 109 | * // Examples migrated from Flow docs 110 | * type ExtractPropType = (arg: T) => T['prop']; 111 | * type Obj = { prop: number }; 112 | * type PropType = $Call>; // number 113 | * 114 | * type ExtractReturnType any> = (arg: T) => ReturnType; 115 | * type Fn = () => number; 116 | * type FnReturnType = $Call>; // number 117 | */ 118 | export type $Call any> = Fn extends ( 119 | arg: any 120 | ) => infer RT 121 | ? RT 122 | : never; 123 | 124 | /** 125 | * $Shape 126 | * @desc Copies the shape of the type supplied, but marks every field optional. 127 | * @see https://flow.org/en/docs/types/utilities/#toc-shape 128 | * @example 129 | * type Props = { name: string; age: number; visible: boolean }; 130 | * 131 | * // Expect: Partial 132 | * type PartialProps = $Shape; 133 | */ 134 | export type $Shape = Partial; 135 | 136 | /** 137 | * $NonMaybeType 138 | * @desc Excludes null and undefined from T 139 | * @see https://flow.org/en/docs/types/utilities/#toc-nonmaybe 140 | * @example 141 | * type MaybeName = string | null; 142 | * 143 | * // Expect: string 144 | * type Name = $NonMaybeType; 145 | */ 146 | export type $NonMaybeType = NonNullable; 147 | 148 | /** 149 | * Class 150 | * @desc Represents constructor of type T 151 | * @see https://flow.org/en/docs/types/utilities/#toc-class 152 | * @example 153 | * class Store {} 154 | * function makeStore(storeClass: Class): Store { 155 | * return new storeClass(); 156 | * } 157 | */ 158 | export type Class = new (...args: any[]) => T; 159 | 160 | /** 161 | * mixed 162 | * @desc An arbitrary type that could be anything 163 | * @see https://flow.org/en/docs/types/mixed 164 | * @example 165 | * 166 | * function stringify(value: mixed) { 167 | * // ... 168 | * } 169 | * 170 | * stringify("foo"); 171 | * stringify(3.14); 172 | * stringify(null); 173 | * stringify({}); 174 | */ 175 | export type mixed = unknown; 176 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/index.ts"], 4 | "compilerOptions": { 5 | "removeComments": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "exclude": ["src/**/*.snap.ts"], 4 | "compilerOptions": { 5 | /* Strict Type-Checking Options */ 6 | "strict": true, 7 | /* Others */ 8 | "lib": ["dom", "es2017"], 9 | "types": ["jest"], 10 | "outDir": "out/", // target for compiled files 11 | "target": "es5", 12 | "jsx": "react", // process JSX 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "declaration": true, // emit declarations 16 | "importHelpers": false, // importing helper functions from tslib 17 | "noEmitHelpers": false, // disable emitting inline helper functions 18 | "noEmitOnError": true, 19 | // "noUnusedLocals": true, 20 | "pretty": true, 21 | "removeComments": true, 22 | "sourceMap": true, 23 | "stripInternal": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended"], 3 | "rules": { 4 | "arrow-parens": false, 5 | "arrow-return-shorthand": [false], 6 | "ban-types": false, 7 | "comment-format": [true, "check-space"], 8 | "import-blacklist": [true, "rxjs"], 9 | "interface-over-type-literal": false, 10 | "interface-name": false, 11 | "max-line-length": false, 12 | "member-access": false, 13 | "member-ordering": [true, { "order": "fields-first" }], 14 | "newline-before-return": false, 15 | "no-any": false, 16 | "no-empty-interface": false, 17 | "no-import-side-effect": [true], 18 | "no-inferrable-types": [true, "ignore-params", "ignore-properties"], 19 | "no-invalid-this": [true, "check-function-in-method"], 20 | "no-namespace": false, 21 | "no-null-keyword": false, 22 | "no-require-imports": false, 23 | "no-submodule-imports": [true, "@src", "rxjs"], 24 | "no-this-assignment": [true, { "allow-destructuring": true }], 25 | "no-trailing-whitespace": true, 26 | "no-unused-expression": [false], 27 | "object-literal-sort-keys": false, 28 | "object-literal-shorthand": false, 29 | "one-variable-per-declaration": [false], 30 | "only-arrow-functions": [true, "allow-declarations"], 31 | "ordered-imports": [false], 32 | "prefer-method-signature": false, 33 | "prefer-template": [true, "allow-single-concat"], 34 | "quotemark": [true, "single", "jsx-double"], 35 | "semicolon": [true, "always", "ignore-bound-class-methods"], 36 | "trailing-comma": [ 37 | true, 38 | { 39 | "singleline": "never", 40 | "multiline": { 41 | "objects": "always", 42 | "arrays": "always", 43 | "functions": "ignore", 44 | "typeLiterals": "ignore" 45 | }, 46 | "esSpecCompliant": true 47 | } 48 | ], 49 | "triple-equals": [true, "allow-null-check"], 50 | "type-literal-delimiter": true, 51 | "typedef": [true, "parameter", "property-declaration"], 52 | "variable-name": [ 53 | true, 54 | "ban-keywords", 55 | "check-format", 56 | "allow-pascal-case", 57 | "allow-leading-underscore" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /utils/test-utils.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export function testType(a?: T): T { 3 | return undefined as any; 4 | } 5 | 6 | // export interface IsType { 7 | // (v: T): '1'; 8 | // (v: any): '0'; 9 | // } 10 | --------------------------------------------------------------------------------