├── .eslintrc.cjs ├── .git2gus └── config.json ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── automerge.yml │ ├── create-github-release.yml │ ├── devScripts.yml │ ├── failureNotifications.yml │ ├── notify-slack-on-pr-open.yml │ ├── onRelease.yml │ ├── test.yml │ └── validate-pr.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .mocharc.json ├── .nycrc ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── launch.json └── settings.json ├── .yarnrc ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPING.md ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── commitlint.config.js ├── package.json ├── src ├── errors.ts ├── experimental │ └── index.ts ├── index.ts ├── narrowing │ ├── as.ts │ ├── assert.ts │ ├── coerce.ts │ ├── ensure.ts │ ├── get.ts │ ├── has.ts │ ├── index.ts │ ├── internal.ts │ ├── is.ts │ ├── object.ts │ └── to.ts └── types │ ├── alias.ts │ ├── collection.ts │ ├── conditional.ts │ ├── function.ts │ ├── index.ts │ ├── json.ts │ ├── mapped.ts │ └── union.ts ├── test ├── .eslintrc.cjs ├── experimental │ └── index.test.ts ├── narrowing │ ├── as.test.ts │ ├── assert.test.ts │ ├── coerce.test.ts │ ├── ensure.test.ts │ ├── get.test.ts │ ├── has.test.ts │ ├── internal.test.ts │ ├── is.test.ts │ ├── object.test.ts │ └── to.test.ts └── tsconfig.json ├── tsconfig.json ├── typedoc.js └── yarn.lock /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint-config-salesforce-typescript', 'eslint-config-salesforce-license'], 3 | rules: { 4 | // Define convenient alias types 5 | '@typescript-eslint/no-empty-interface': 'off', 6 | '@typescript-eslint/ban-types': 'off' 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /.git2gus/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "productTag": "a1aB00000004Bx8IAE", 3 | "defaultBuild": "offcore.tooling.54" 4 | } 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | #ECCN: Open Source 2 | #GUSINFO:Open Source,Open Source Workflow 3 | 4 | # For more info on this file syntax: 5 | # https://help.github.com/en/articles/about-code-owners 6 | 7 | # These owners will be the default owners for everything in 8 | # the repo. 9 | 10 | * @forcedotcom/platform-cli 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | day: 'saturday' 8 | versioning-strategy: 'increase' 9 | labels: 10 | - 'dependencies' 11 | open-pull-requests-limit: 5 12 | pull-request-branch-name: 13 | separator: '-' 14 | commit-message: 15 | # cause a release for non-dev-deps 16 | prefix: fix(deps) 17 | # no release for dev-deps 18 | prefix-development: chore(dev-deps) 19 | ignore: 20 | - dependency-name: '@salesforce/dev-scripts' 21 | - dependency-name: '*' 22 | update-types: ['version-update:semver-major'] 23 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: automerge 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '42 2,5,8,11 * * *' 6 | 7 | jobs: 8 | automerge: 9 | uses: salesforcecli/github-workflows/.github/workflows/automerge.yml@main 10 | secrets: inherit 11 | -------------------------------------------------------------------------------- /.github/workflows/create-github-release.yml: -------------------------------------------------------------------------------- 1 | name: create-github-release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - prerelease/** 8 | tags-ignore: 9 | - "*" 10 | workflow_dispatch: 11 | inputs: 12 | prerelease: 13 | type: string 14 | description: "Name to use for the prerelease: beta, dev, etc. NOTE: If this is already set in the package.json, it does not need to be passed in here." 15 | 16 | jobs: 17 | release: 18 | uses: salesforcecli/github-workflows/.github/workflows/create-github-release.yml@main 19 | secrets: inherit 20 | with: 21 | prerelease: ${{ inputs.prerelease }} 22 | # If this is a push event, we want to skip the release if there are no semantic commits 23 | # However, if this is a manual release (workflow_dispatch), then we want to disable skip-on-empty 24 | # This helps recover from forgetting to add semantic commits ('fix:', 'feat:', etc.) 25 | skip-on-empty: ${{ github.event_name == 'push' }} 26 | docs: 27 | # Most repos won't use this 28 | # Depends on the 'release' job to avoid git collisions, not for any functionality reason 29 | needs: release 30 | secrets: inherit 31 | if: ${{ github.ref_name == 'main' }} 32 | uses: salesforcecli/github-workflows/.github/workflows/publishTypedoc.yml@main 33 | -------------------------------------------------------------------------------- /.github/workflows/devScripts.yml: -------------------------------------------------------------------------------- 1 | name: devScripts 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '50 6 * * 0' 6 | 7 | jobs: 8 | update: 9 | uses: salesforcecli/github-workflows/.github/workflows/devScriptsUpdate.yml@main 10 | secrets: inherit 11 | -------------------------------------------------------------------------------- /.github/workflows/failureNotifications.yml: -------------------------------------------------------------------------------- 1 | name: failureNotifications 2 | on: 3 | workflow_run: 4 | workflows: 5 | - publish 6 | - create-github-release 7 | types: 8 | - completed 9 | jobs: 10 | failure-notify: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.workflow_run.conclusion == 'failure' }} 13 | steps: 14 | - name: Announce Failure 15 | id: slack 16 | uses: slackapi/slack-github-action@v1.26.0 17 | env: 18 | # for non-CLI-team-owned plugins, you can send this anywhere you like 19 | SLACK_WEBHOOK_URL: ${{ secrets.CLI_ALERTS_SLACK_WEBHOOK }} 20 | SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK 21 | with: 22 | # Payload can be visually tested here: https://app.slack.com/block-kit-builder/T01GST6QY0G#%7B%22blocks%22:%5B%5D%7D 23 | # Only copy over the "blocks" array to the Block Kit Builder 24 | payload: | 25 | { 26 | "text": "Workflow \"${{ github.event.workflow_run.name }}\" failed in ${{ github.event.workflow_run.repository.name }}", 27 | "blocks": [ 28 | { 29 | "type": "header", 30 | "text": { 31 | "type": "plain_text", 32 | "text": ":bh-alert: Workflow \"${{ github.event.workflow_run.name }}\" failed in ${{ github.event.workflow_run.repository.name }} :bh-alert:" 33 | } 34 | }, 35 | { 36 | "type": "section", 37 | "text": { 38 | "type": "mrkdwn", 39 | "text": "*Repo:* ${{ github.event.workflow_run.repository.html_url }}\n*Workflow name:* `${{ github.event.workflow_run.name }}`\n*Job url:* ${{ github.event.workflow_run.html_url }}" 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/notify-slack-on-pr-open.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Slack Notification 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Notify Slack on PR open 12 | env: 13 | WEBHOOK_URL: ${{ secrets.CLI_TEAM_SLACK_WEBHOOK_URL }} 14 | PULL_REQUEST_AUTHOR_ICON_URL: ${{ github.event.pull_request.user.avatar_url }} 15 | PULL_REQUEST_AUTHOR_NAME: ${{ github.event.pull_request.user.login }} 16 | PULL_REQUEST_AUTHOR_PROFILE_URL: ${{ github.event.pull_request.user.html_url }} 17 | PULL_REQUEST_BASE_BRANCH_NAME: ${{ github.event.pull_request.base.ref }} 18 | PULL_REQUEST_COMPARE_BRANCH_NAME: ${{ github.event.pull_request.head.ref }} 19 | PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} 20 | PULL_REQUEST_REPO: ${{ github.event.pull_request.head.repo.name }} 21 | PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} 22 | PULL_REQUEST_URL: ${{ github.event.pull_request.html_url }} 23 | uses: salesforcecli/github-workflows/.github/actions/prNotification@main 24 | -------------------------------------------------------------------------------- /.github/workflows/onRelease.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: 3 | release: 4 | # both release and prereleases 5 | types: [published] 6 | # support manual release in case something goes wrong and needs to be repeated or tested 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: github tag that needs to publish 11 | type: string 12 | required: true 13 | jobs: 14 | getDistTag: 15 | outputs: 16 | tag: ${{ steps.distTag.outputs.tag }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | ref: ${{ github.event.release.tag_name || inputs.tag }} 22 | - uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main 23 | id: distTag 24 | npm: 25 | uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml@main 26 | needs: [getDistTag] 27 | with: 28 | tag: ${{ needs.getDistTag.outputs.tag || 'latest' }} 29 | githubTag: ${{ github.event.release.tag_name || inputs.tag }} 30 | 31 | secrets: inherit 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | branches-ignore: [main] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | yarn-lockfile-check: 9 | uses: salesforcecli/github-workflows/.github/workflows/lockFileCheck.yml@main 10 | linux-unit-tests: 11 | needs: yarn-lockfile-check 12 | uses: salesforcecli/github-workflows/.github/workflows/unitTestsLinux.yml@main 13 | windows-unit-tests: 14 | needs: yarn-lockfile-check 15 | uses: salesforcecli/github-workflows/.github/workflows/unitTestsWindows.yml@main 16 | compile-everywhere: 17 | needs: [linux-unit-tests] 18 | uses: salesforcecli/github-workflows/.github/workflows/externalCompile.yml@main 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | externalProjectGitUrl: 23 | - https://github.com/salesforcecli/plugin-auth 24 | - https://github.com/salesforcecli/plugin-data 25 | - https://github.com/salesforcecli/plugin-dev 26 | - https://github.com/salesforcecli/plugin-source 27 | - https://github.com/salesforcecli/plugin-deploy-retrieve 28 | - https://github.com/salesforcecli/plugin-user 29 | - https://github.com/salesforcecli/sf-plugins-core 30 | - https://github.com/forcedotcom/kit 31 | - https://github.com/forcedotcom/sfdx-core 32 | - https://github.com/forcedotcom/source-deploy-retrieve 33 | - https://github.com/forcedotcom/source-tracking 34 | - https://github.com/forcedotcom/ts-sinon 35 | with: 36 | packageName: '@salesforce/ts-types' 37 | externalProjectGitUrl: ${{ matrix.externalProjectGitUrl }} 38 | command: 'yarn build' 39 | -------------------------------------------------------------------------------- /.github/workflows/validate-pr.yml: -------------------------------------------------------------------------------- 1 | name: pr-validation 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, edited] 6 | # only applies to PRs that want to merge to main 7 | branches: [main] 8 | 9 | jobs: 10 | pr-validation: 11 | uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # -- CLEAN 2 | 3 | # use yarn by default, so ignore npm 4 | package-lock.json 5 | 6 | # never checkin npm config 7 | .npmrc 8 | 9 | # debug logs 10 | npm-error.log 11 | yarn-error.log 12 | lerna-debug.log 13 | 14 | # compile source 15 | lib 16 | 17 | # test artifacts 18 | *xunit.xml 19 | *checkstyle.xml 20 | *unitcoverage 21 | .nyc_output 22 | coverage 23 | 24 | # generated docs 25 | docs 26 | 27 | # -- CLEAN ALL 28 | *.tsbuildinfo 29 | .eslintcache 30 | .wireit 31 | node_modules 32 | 33 | # -- 34 | # put files here you don't want cleaned with sf-clean 35 | 36 | # os specific files 37 | .DS_Store 38 | .idea -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint && yarn pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn build && yarn test 5 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register,source-map-support/register", 3 | "watch-extensions": "ts", 4 | "recursive": true, 5 | "reporter": "spec", 6 | "timeout": 5000 7 | } 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "nyc": { 3 | "extends": "@salesforce/dev-config/nyc", 4 | "lines": 100, 5 | "statements": 100, 6 | "functions": 100, 7 | "branches": 100 8 | } 9 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | packages/kit/vendor/lodash.js -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@salesforce/prettier-config" 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach", 9 | "port": 9229, 10 | "request": "attach", 11 | "skipFiles": ["/**"], 12 | "type": "pwa-node" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "npm.packageManager": "yarn" 4 | } 5 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npmjs.org" 2 | save-exact false 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.12](https://github.com/forcedotcom/ts-types/compare/2.0.11...2.0.12) (2024-07-29) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * typedoc update ([#307](https://github.com/forcedotcom/ts-types/issues/307)) ([2aebc45](https://github.com/forcedotcom/ts-types/commit/2aebc453f0bc57e206d762d3d5332966dc85b7c1)) 7 | 8 | 9 | 10 | ## [2.0.11](https://github.com/forcedotcom/ts-types/compare/2.0.10...2.0.11) (2024-07-29) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **deps:** devScripts update ([#306](https://github.com/forcedotcom/ts-types/issues/306)) ([baf2277](https://github.com/forcedotcom/ts-types/commit/baf22772314019f9ae48c5700d03112ec6160aff)) 16 | 17 | 18 | 19 | ## [2.0.10](https://github.com/forcedotcom/ts-types/compare/2.0.9...2.0.10) (2024-06-13) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **deps:** bump braces from 3.0.2 to 3.0.3 ([a73e5ca](https://github.com/forcedotcom/ts-types/commit/a73e5ca88b18a5a96884e6e72dd9edb1d511cdf8)) 25 | 26 | 27 | 28 | ## [2.0.9](https://github.com/forcedotcom/ts-types/compare/2.0.8...2.0.9) (2023-10-17) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * **deps:** bump @babel/traverse from 7.15.0 to 7.23.2 ([71c5bf5](https://github.com/forcedotcom/ts-types/commit/71c5bf565affbd3157250b327534c2156261b364)) 34 | 35 | 36 | 37 | ## [2.0.8](https://github.com/forcedotcom/ts-types/compare/2.0.7...2.0.8) (2023-09-28) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **deps:** bump get-func-name from 2.0.0 to 2.0.2 ([6bf5cd6](https://github.com/forcedotcom/ts-types/commit/6bf5cd68532ce3eb43a9b02ebe242a4917860558)) 43 | 44 | 45 | 46 | ## [2.0.7](https://github.com/forcedotcom/ts-types/compare/2.0.6...2.0.7) (2023-08-19) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **deps:** bump tslib from 2.6.1 to 2.6.2 ([5be15ad](https://github.com/forcedotcom/ts-types/commit/5be15ad976b963ac997140892b30fa4e223d1646)) 52 | 53 | 54 | 55 | ## [2.0.6](https://github.com/forcedotcom/ts-types/compare/2.0.5...2.0.6) (2023-07-30) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * **deps:** bump tslib from 2.6.0 to 2.6.1 ([db04dd0](https://github.com/forcedotcom/ts-types/commit/db04dd0d58c364bc2a9f57d59868a756246051c4)) 61 | 62 | 63 | 64 | ## [2.0.5](https://github.com/forcedotcom/ts-types/compare/2.0.4...2.0.5) (2023-07-11) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **deps:** bump semver from 5.7.1 to 5.7.2 ([4324551](https://github.com/forcedotcom/ts-types/commit/43245514cb788a4985f99b3c6782382559443d66)) 70 | 71 | 72 | 73 | ## [2.0.4](https://github.com/forcedotcom/ts-types/compare/2.0.3...2.0.4) (2023-07-02) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * **deps:** bump tslib from 2.5.3 to 2.6.0 ([87ed10d](https://github.com/forcedotcom/ts-types/commit/87ed10d09458a472900f7e700b950071648e72d8)) 79 | 80 | 81 | 82 | ## [2.0.3](https://github.com/forcedotcom/ts-types/compare/2.0.2...2.0.3) (2023-06-03) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * **deps:** bump tslib from 2.5.2 to 2.5.3 ([a64499d](https://github.com/forcedotcom/ts-types/commit/a64499d1085b412357e9ce0d8576eb3dd0a4c0b1)) 88 | 89 | 90 | 91 | ## [2.0.2](https://github.com/forcedotcom/ts-types/compare/2.0.1...2.0.2) (2023-05-21) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * **deps:** bump tslib from 2.5.0 to 2.5.2 ([1eda8c8](https://github.com/forcedotcom/ts-types/commit/1eda8c88fc2767f4108b0e7c1d7721680fb75764)) 97 | 98 | 99 | 100 | ## [2.0.1](https://github.com/forcedotcom/ts-types/compare/1.7.3...2.0.1) (2023-05-03) 101 | 102 | 103 | 104 | ## [1.7.3](https://github.com/forcedotcom/ts-types/compare/1.7.2...1.7.3) (2023-01-29) 105 | 106 | 107 | ### Bug Fixes 108 | 109 | * **deps:** bump tslib from 2.4.1 to 2.5.0 ([214f0df](https://github.com/forcedotcom/ts-types/commit/214f0df4d37246525deb2de14f8a23e32377a1c0)) 110 | 111 | 112 | 113 | ## [1.7.2](https://github.com/forcedotcom/ts-types/compare/1.7.1...1.7.2) (2023-01-07) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * **deps:** bump json5 from 1.0.1 to 1.0.2 ([759e7dd](https://github.com/forcedotcom/ts-types/commit/759e7dd653db241a349b8c94b7576448b6195530)) 119 | 120 | 121 | 122 | ## [1.7.1](https://github.com/forcedotcom/ts-types/compare/1.7.0...1.7.1) (2022-11-05) 123 | 124 | 125 | ### Bug Fixes 126 | 127 | * **deps:** bump tslib from 2.4.0 to 2.4.1 ([a190490](https://github.com/forcedotcom/ts-types/commit/a190490de96f0aebe8ec6b8bdcce7be28de21529)) 128 | 129 | 130 | 131 | # [1.7.0](https://github.com/forcedotcom/ts-types/compare/4a4efbb62ea58bb4396839449182be4217dbcbf2...1.7.0) (2022-10-22) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * publish api docs ([533448e](https://github.com/forcedotcom/ts-types/commit/533448e70c5416a974340e7449077a0f3288358d)) 137 | * update deps and publish gh-pages ([27a498a](https://github.com/forcedotcom/ts-types/commit/27a498a306fc68d6db0f9b9d1cf28d3ae1ea7534)) 138 | * updated nycrc ([6f1df04](https://github.com/forcedotcom/ts-types/commit/6f1df04ffcc8c8bcdc4cf9d890d911ba938386d3)) 139 | 140 | 141 | ### Features 142 | 143 | * code refactoring ([4a4efbb](https://github.com/forcedotcom/ts-types/commit/4a4efbb62ea58bb4396839449182be4217dbcbf2)) 144 | * remove experimental "view" functions ([#84](https://github.com/forcedotcom/ts-types/issues/84)) ([5a763df](https://github.com/forcedotcom/ts-types/commit/5a763df98e0de118e664eb91e32771a0d6dc5952)) 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Techical writers will be added as reviewers on markdown changes. 2 | *.md @forcedotcom/cli-docs 3 | 4 | # Comment line immediately above ownership line is reserved for related other information. Please be careful while editing. 5 | #ECCN:Open Source 5D002 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | - Using welcoming and inclusive language 39 | - Being respectful of differing viewpoints and experiences 40 | - Gracefully accepting constructive criticism 41 | - Focusing on what is best for the community 42 | - Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | - The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | - Personal attacks, insulting/derogatory comments, or trolling 49 | - Public or private harassment 50 | - Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | - Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | - Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org 'https://www.contributor-covenant.org/' 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Familiarize yourself with the codebase and the [development](DEVELOPING.md) doc. 4 | 1. Create a new issue before starting your project so that we can keep track of 5 | what you are trying to add/fix. That way, we can also offer suggestions or 6 | let you know if there is already an effort in progress. 7 | 1. Fork this repository. 8 | 1. Create a _topic_ branch in your fork based on the correct branch (usually the **main** branch, see [Branches section](#branches) below). Note, this step is recommended but technically not required if contributing using a fork. 9 | 1. Edit the code in your fork. 10 | 1. Write appropriate tests for your changes. Try to achieve at least 95% code coverage on any new code. No pull request will be accepted without unit tests. 11 | 1. Sign CLA (see [CLA](#cla) below) 12 | 1. Send us a pull request when you are done. We'll review your code, suggest any 13 | needed changes, and merge it in. 14 | 15 | ## CLA 16 | 17 | External contributors will be required to sign a Contributor's License 18 | Agreement. You can do so by going to https://cla.salesforce.com/sign-cla. 19 | 20 | ## Branches 21 | 22 | - We work in branches off of `main`. 23 | - Our released (aka. _production_) branch is `main`. 24 | - Our work happens in _topic_ branches (feature and/or bug-fix). 25 | - feature as well as bug-fix branches are based on `main` 26 | - branches _should_ be kept up-to-date using `rebase` 27 | - see below for further merge instructions 28 | 29 | ### Merging between branches 30 | 31 | - We try to limit merge commits as much as possible. 32 | 33 | - They are usually only ok when done by our release automation. 34 | 35 | - _Topic_ branches are: 36 | 37 | 1. based on `main` and will be 38 | 1. squash-merged into `main`. 39 | 40 | ### Releasing 41 | 42 | - A new version of this library will be published upon merging PRs to `main`, with the version number increment based on commitizen. 43 | 44 | ## Pull Requests 45 | 46 | - Develop features and bug fixes in _topic_ branches. 47 | - _Topic_ branches can live in forks (external contributors) or within this repository (committers). 48 | \*\* When creating _topic_ branches in this repository please prefix with `/`. 49 | 50 | ### Merging Pull Requests 51 | 52 | - Pull request merging is restricted to squash & merge only. 53 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing 2 | 3 | ## Pre-requisites 4 | 5 | 1. We use the active NodeJS LTS. If you need to work with multiple versions of Node, you 6 | might consider using [nvm](https://github.com/creationix/nvm). 7 | 1. This repository uses [yarn](https://yarnpkg.com/) to manage node dependencies. Please install yarn globally using `npm install --global yarn`. 8 | 1. Tests are executed on the latest NodeJS as well as all active and maintained NodeJS LTS versions. 9 | 10 | ## Structure 11 | 12 | ### Packages 13 | 14 | The packages directory contains the different npm packages. 15 | 16 | ## Typical workflow 17 | 18 | You would only do this once after you cloned the repository. 19 | 20 | 1. Clone this repository from git. 21 | 1. `cd` into `sfdx-dev-packages`. 22 | 1. We develop using feature brances off `main` and release from the `main` branch. At 23 | this point, it should be set to `main` by default. If not, run `git checkout -t origin/main`. 24 | 1. `yarn` to bring in all the top-level dependencies and bootstrap. 25 | 1. Open the project in your editor of choice. 26 | 27 | ## When you are ready to commit 28 | 29 | 1. We enforce commit message format. We recommend using [commitizen](https://github.com/commitizen/cz-cli) by installing it with `yarn global add commitizen` then commit using `git cz` which will prompt you questions to format the commit message. 30 | 1. Before commit and push, husky will run several hooks to ensure the message and that everything lints and compiles properly. 31 | 32 | ## List of Useful commands 33 | 34 | _These commands assume that they are executed from the top-level directory. 35 | Internally, they delegate to `lerna` to call them on each npm module in the 36 | packages directory._ 37 | 38 | ### `yarn bootstrap` 39 | 40 | This bootstraps the packages by issuing a `yarn install` on each package and 41 | also symlinking any package that are part of the packages folder. 42 | 43 | You would want do this as the first step after you have made changes in the 44 | modules. 45 | 46 | If you change the dependencies in your package.json, you will also need to run 47 | this command. 48 | 49 | ### `yarn compile` 50 | 51 | This runs `yarn compile` on each of the package in packages. 52 | 53 | ### `yarn clean` 54 | 55 | This run `yarn clean` on each of the package in packages. Running `yarn cleal-all` will also clean up the node_module directories. 56 | 57 | ### `yarn test` 58 | 59 | This runs `yarn test` on each of the packages. 60 | 61 | ### `yarn lint` 62 | 63 | This runs `yarn lint` on each of the packages. 64 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, Salesforce.com, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Types and tools for Salesforce TypeScript development 2 | 3 | ## What is this? 4 | 5 | This is a simple TypeScript-oriented library developed for use in Salesforce TypeScript libraries, applications, and plugins consisting of two parts: 6 | 7 | 1. A collection of commonly desired types. 8 | 1. A collection of type-narrowing convenience functions for writing concise type-guards. 9 | 10 | See the [API documentation](https://forcedotcom.github.io/ts-types) for more details on each of the utilities that `ts-types` provides. 11 | 12 | ## Why did we create it? 13 | 14 | We were interested in developing with _strict_ compiler settings in TypeScript. Among the sub-settings that comprise strict mode are `strictNullChecks`, `strictPropertyInitialization`, and `noImplicitAny`. `tslint` provides additional rules that further improve code quality and type correctness by discouraging explicit `any` usages and other unsafe constructs such as some classes of unwarranted type assertions. Together, these settings have the potential to increase code quality substantially, reducing the frequency of certain classes of runtime errors typical in classical JavaScript applications. They also encourage the writing of clearer, more accurately typed code, which helps teams work together more effectively and more rapidly onboard new hires. 15 | 16 | Of course, stricter compiler settings require developers to write more type-safe code -- or to work around the compiler's insistence on type-safety. Often this stricter style leads to more verbose code in the way of type declarations and type guards, and can require new and occasionally unfamiliar patterns to accomplish without subverting the compiler's enforcement of the type system (typically via the use of type assertions). 17 | 18 | TypeScript provides both syntax and built-in types designed to help write well-typed code, but we can be more terse and concise by employing additional types and type-narrowing utility functions by leveraging language features like type predicates backed by sound runtime validation, simplified `undefined` and `null` checking, etc. That's where this library comes in. 19 | 20 | ## How will it help me? 21 | 22 | This library has its roots in solving the problem of how to handle untyped JSON data in a type-safe way. It was born when we added some basic type declarations to replace the unsafe `any` type as a stand-in for JSON data with a type that could capture the range of data available in any JSON construct. This yielded the `AnyJson` type, which is effectively a union of all primitive and collection JSON values. The type alone was not enough to make for convenient, type-guarded handling of JSON data, however. TypeScript supports a very elegant system of control flow analysis that will narrow the type of a variable in code after the compiler can prove the set of possible types of the variable has been reduced. Using type guards in your code improves its runtime type safety characteristics, makes it more readable, and provides richer typing information for IDEs. Type guards are implemented as conditional statements, however, and can quickly become noisy and make what was once terse JavaScript code expand into several lines of type checking. This library aimed to simplify the experience of reducing the amount of type guards needed to process a typed-JSON data structure by providing several convenience functions that help extract well-typed data from such JSON structures. 23 | 24 | For example, look at the following typical untyped JSON processing in JavaScript: 25 | 26 | ```javascript 27 | // concise, but not at all null-safe or type-safe; often made to be at least null-safe using lodash fns 28 | JSON.parse(response.body).results.forEach(item => db.save(item.id, item)); 29 | ``` 30 | 31 | Then a safe version in bare TypeScript using type guards: 32 | 33 | ```typescript 34 | const json = JSON.parse(response.body); 35 | // type of json -> `any`, but will not be undefined or JSON.parse would throw 36 | if (json === null && typeof json !== 'object') throw new Error('Unexpected json data type'); 37 | let results = json.results; 38 | // type of results -> `any` 39 | if (!Array.isArray(results)) results = []; 40 | // type of results -> `any[]` 41 | results.forEach(item => { 42 | // type of item -> `any` 43 | const id = item.id; 44 | // type of id -> `any` 45 | if (typeof id !== 'string') throw new Error('Unexpected item id data type'); 46 | // type of id -> `string` 47 | db.save(id, item); 48 | }); 49 | ``` 50 | 51 | While that's pretty safe, it's also a mess to read and write. That's why this library is here to help! 52 | 53 | ```typescript 54 | const json = ensureJsonMap(JSON.parse(response.body)); 55 | // type of json -> `JsonMap` or raises an error 56 | const results = asJsonArray(json.results, []); 57 | // type of results -> `JsonArray` or uses the default of `[]` 58 | results.forEach(item => { 59 | // type of item -> `AnyJson` 60 | record = ensureJsonMap(record); 61 | db.save(ensureString(record.id), record); 62 | }); 63 | ``` 64 | 65 | Removing the comments, we can shorten the above somewhat to achieve something not much more complex than the original example, but with robust type and null checking implemented: 66 | 67 | ```typescript 68 | asJsonArray(ensureJsonMap(JSON.parse(response.body)).results, []).forEach(item => { 69 | const record = ensureJsonMap(item); 70 | db.save(ensureString(record.id), record); 71 | }); 72 | ``` 73 | 74 | The `ensure*` functions are used in this example since they will raise an error when the value being checked either does not exist or does not match the expected type. Additionally, and perhaps more importantly, the generic `any` and `AnyJson` types get progressively narrowed when using these functions to more specific types. Of course, you don't always want to raise an error when these conditions are not met, so alternative forms exist for each of the JSON data types that allow the types to be tested and narrowed -- see the `is*` and `as*` variants in the API documentation for testing and narrowing capabilities without additionally raising errors. 75 | 76 | ### Beyond JSON 77 | 78 | After a few iterations of working on the JSON support types and utilities, it became apparent that we needed other non-JSON types and functions that provide similar capabilities. Rather than create a new library for those, we instead grew the scope of this one to contain all of our commonly used types and narrowing functions. 79 | 80 | ### Types 81 | 82 | A small library of types is included to help write more concise TypeScript code. These types are in part designed to augment the standard types included with the TypeScript library. Please see the generated API documentation for the complete set of provided types. Here are a few of the most commonly used types: 83 | 84 | - **Optional<T>**: An alias for the union type `T | undefined`. 85 | - **NonOptional<T>**: Subtracts `undefined` from a type `T`, when `T` includes `undefined` as a union member. 86 | - **Nullable<T>**: An alias for the union type `Optional`, or `T | null | undefined`. `NonNullable` is a TypeScript built-in that subtracts both `null` and `undefined` from a type `T` with either as a union member. 87 | - **Dictionary<T=unknown>**: An alias for a `string`-indexed `object` of the form `{ [key: string]: Optional }`. 88 | - **KeyOf<T>**: An alias for the commonly needed yet verbose `Extract`. 89 | - **AnyJson**: A union type of all valid JSON values, equal to `JsonPrimitive | JsonCollection`. 90 | - **JsonPrimitive**: A union of all valid JSON primitive values, qual to `null | string | number| boolean`. 91 | - **JsonMap**: A dictionary of any valid JSON value, defined as `Dictionary`. 92 | - **JsonArray**: An array of any valid JSON value, defined as `Array`. 93 | - **JsonCollection**: A union of all JSON collection types, equal to `JsonMap | JsonArray`. 94 | 95 | ### Narrowing functions 96 | 97 | This library provides several categories of functions to help with safely narrowing variables of broadly typed variables, like `unknown` or `object`, to more specific types. 98 | 99 | #### is\* 100 | 101 | The `is*` suite of functions accept a variable of a broad type such as `unknown` or `object` and returns a `boolean` type-predicate useful for narrowing the type in conditional scopes. 102 | 103 | ```typescript 104 | // type of value -> string | boolean 105 | if (isString(value)) { 106 | // type of value -> string 107 | } 108 | // type of value -> boolean 109 | ``` 110 | 111 | #### as\* 112 | 113 | The `as*` suite of functions accept a variable of a broad type such as `unknown` or `object` and optionally returns a narrowed type after validating it with a runtime test. If the test is negative or if the value was not defined (i.e. `undefined` or `null`), `undefined` is returned instead. 114 | 115 | ```typescript 116 | // some function that takes a string or undefined 117 | function upperFirst(s: Optional): Optional { 118 | return s ? s.charAt(0).toUpperCase() + s.slice(1) : s; 119 | } 120 | // type of value -> unknown 121 | const name = upperFirst(asString(value)); 122 | // type of name -> Optional 123 | ``` 124 | 125 | #### ensure\* 126 | 127 | The `ensure*` suite of functions narrow values' types to a definite value of the designated type, or raises an error if the value is `undefined` or of an incompatible type. 128 | 129 | ```typescript 130 | // type of value -> unknown 131 | try { 132 | const s = ensureString(value); 133 | // type of s -> string 134 | } catch (err) { 135 | // s was undefined, null, or not of type string 136 | } 137 | ``` 138 | 139 | #### has\* 140 | 141 | The `has*` suite of functions both tests for the existence and type-compatibility of a given value and, if the runtime value check succeeds, narrows the type to a view of the original value's type intersected with the tested property (e.g. `T & { [_ in K]: V }` where `K` is the test property key and `V` is the test property value type). 142 | 143 | ```typescript 144 | // type of value -> unknown 145 | if (hasString(value, 'name')) { 146 | // type of value -> { name: string } 147 | // value can be further narrowed with additional checks 148 | if (hasArray(value, 'results')) { 149 | // type of value -> { name: string } & { results: unknown[] } 150 | } else if (hasInstance(value, 'error', Error)) { 151 | // type of value -> { name: string } & { error: Error } 152 | } 153 | } 154 | ``` 155 | 156 | #### get\* 157 | 158 | The `get*` suite of functions search an `unknown` target value for a given path. Search paths follow the same syntax as `lodash`'s `get`, `set`, `at`, etc. These functions are more strictly typed, however, increasingly the likelihood that well-typed code stays well-typed as a function's control flow advances. 159 | 160 | ```typescript 161 | // imagine response json retrieved from a remote query 162 | const response = { 163 | start: 0, 164 | length: 2, 165 | results: [{ name: 'first' }, { name: 'second' }] 166 | }; 167 | const nameOfFirst = getString(response, 'results[0].name'); 168 | // type of nameOfFirst = string 169 | ``` 170 | 171 | #### coerce\* 172 | 173 | The `coerce` suite of functions accept values of general types and narrow their types to JSON-specific values. They are named with the `coerce` prefix to indicate that they do not perform an exhaustive runtime check of the entire data structure -- only shallow type checks are performed. As a result, _only_ use these functions when you are confident that the broadly typed subject being coerced was derived from a JSON-compatible value. If you are unsure of an object's origins or contents but want to avoid runtime errors handling elements, see the `to*` set of functions. 174 | 175 | ```typescript 176 | const response = coerceJsonMap(JSON.parse(await http.get('http://example.com/data.json').body)); 177 | // type of response -> JsonMap 178 | ``` 179 | 180 | #### to\* 181 | 182 | The `to*` suite of functions is a fully type-safe version of the `coerce*` functions for JSON narrowing, but at the expense of some runtime performance. Under the hood, the `to*` functions perform a JSON-clone of their subject arguments, ensuring that the entire data structure is JSON compatible before returning the narrowed type. 183 | 184 | ```typescript 185 | const obj = { 186 | name: 'example', 187 | parse: function(s) { 188 | return s.split(':'); 189 | } 190 | }; 191 | const json = toJsonMap(obj); 192 | // type of json -> JsonMap 193 | // json = { name: 'example' } 194 | // notice that the parse function has been omitted to ensure JSON-compatibility! 195 | ``` 196 | 197 | #### object utilities 198 | 199 | This suite of functions are used to iterate the keys, entries, and values of objects with some typing conveniences applied that are not present in their built-in counterparts (i.e. `Object.keys`, `Object.entries`, and `Object.values`), but come with some caveats noted in their documentation. Typical uses include iterating over the properties of an object with more useful `keyof` typings applied during the iterator bodies, and/or filtering out `undefined` or `null` values before invoking the iterator functions. 200 | 201 | ```typescript 202 | const pets: Dictionary = { 203 | fido: 'dog', 204 | bill: 'cat', 205 | fred: undefined 206 | }; 207 | // note that the array is typed as [string, string] rather than [string, string | undefined] 208 | function logPet([name, type]: [string, string]) { 209 | console.log('%s is a %s', name, type); 210 | } 211 | definiteEntriesOf(pets).forEach(logPet); 212 | // fido is a dog 213 | // bill is a cat 214 | ``` 215 | 216 | ## References 217 | 218 | Another Salesforce TypeScript library, [@salesforce/kit](https://www.npmjs.com/package/@salesforce/kit), builds on this library to add additional utilities. It includes additional JSON support, a lightweight replacement for some `lodash` functions, and growing support for patterns used in other Salesforce CLI libraries and applications. 219 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@salesforce/ts-types", 3 | "version": "2.0.12", 4 | "description": "Types and related utilities for TypeScript", 5 | "main": "lib/index.js", 6 | "repository": "forcedotcom/ts-types", 7 | "author": "Salesforce", 8 | "license": "BSD-3-Clause", 9 | "types": "lib/index.d.ts", 10 | "engines": { 11 | "node": ">=18.0.0" 12 | }, 13 | "files": [ 14 | "lib/**/*.js", 15 | "lib/**/*.d.ts" 16 | ], 17 | "scripts": { 18 | "build": "wireit", 19 | "clean": "sf-clean", 20 | "clean-all": "sf-clean all", 21 | "compile": "wireit", 22 | "docs": "sf-docs", 23 | "format": "wireit", 24 | "link-check": "wireit", 25 | "lint": "wireit", 26 | "prepack": "sf-prepack", 27 | "prepare": "sf-install", 28 | "test": "wireit", 29 | "test:only": "wireit" 30 | }, 31 | "dependencies": {}, 32 | "devDependencies": { 33 | "@salesforce/dev-scripts": "^10.2.7", 34 | "ts-node": "^10.9.2", 35 | "typescript": "^5.5.4" 36 | }, 37 | "wireit": { 38 | "build": { 39 | "dependencies": [ 40 | "compile", 41 | "lint" 42 | ] 43 | }, 44 | "compile": { 45 | "command": "tsc -p . --pretty --incremental", 46 | "files": [ 47 | "src/**/*.ts", 48 | "**/tsconfig.json", 49 | "messages/**" 50 | ], 51 | "output": [ 52 | "lib/**", 53 | "*.tsbuildinfo" 54 | ], 55 | "clean": "if-file-deleted" 56 | }, 57 | "format": { 58 | "command": "prettier --write \"+(src|test|schemas)/**/*.+(ts|js|json)|command-snapshot.json\"", 59 | "files": [ 60 | "src/**/*.ts", 61 | "test/**/*.ts", 62 | "schemas/**/*.json", 63 | "command-snapshot.json", 64 | ".prettier*" 65 | ], 66 | "output": [] 67 | }, 68 | "lint": { 69 | "command": "eslint src test --color --cache --cache-location .eslintcache", 70 | "files": [ 71 | "src/**/*.ts", 72 | "test/**/*.ts", 73 | "messages/**", 74 | "**/.eslint*", 75 | "**/tsconfig.json" 76 | ], 77 | "output": [] 78 | }, 79 | "test:compile": { 80 | "command": "tsc -p \"./test\" --pretty", 81 | "files": [ 82 | "test/**/*.ts", 83 | "**/tsconfig.json" 84 | ], 85 | "output": [] 86 | }, 87 | "test": { 88 | "dependencies": [ 89 | "test:only", 90 | "test:compile", 91 | "link-check" 92 | ] 93 | }, 94 | "test:only": { 95 | "command": "nyc mocha \"test/**/*.test.ts\"", 96 | "env": { 97 | "FORCE_COLOR": "2" 98 | }, 99 | "files": [ 100 | "test/**/*.ts", 101 | "src/**/*.ts", 102 | "**/tsconfig.json", 103 | ".mocha*", 104 | "!*.nut.ts", 105 | ".nycrc" 106 | ], 107 | "output": [] 108 | }, 109 | "link-check": { 110 | "command": "node -e \"process.exit(process.env.CI ? 0 : 1)\" || linkinator \"**/*.md\" --skip \"CHANGELOG.md|node_modules|test/|confluence.internal.salesforce.com|my.salesforce.com|%s\" --markdown --retry --directory-listing --verbosity error", 111 | "files": [ 112 | "./*.md", 113 | "./!(CHANGELOG).md", 114 | "messages/**/*.md" 115 | ], 116 | "output": [] 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * A minimal `NamedError` implementation not intended for widespread use -- just enough to support this library's needs. 10 | * For a complete `NamedError` solution, see [@salesforce/kit]{@link https://preview.npmjs.com/package/@salesforce/kit}. 11 | */ 12 | export class NamedError extends Error { 13 | public readonly name: string; 14 | 15 | public constructor(name: string, message: string) { 16 | super(message); 17 | this.name = name; 18 | } 19 | } 20 | 21 | /** 22 | * Indicates an unexpected type was encountered during a type-narrowing operation. 23 | */ 24 | export class AssertionFailedError extends NamedError { 25 | public constructor(message: string) { 26 | super('AssertionFailedError', message); 27 | } 28 | } 29 | 30 | /** 31 | * Indicates an unexpected type was encountered during a type-narrowing operation. 32 | */ 33 | export class UnexpectedValueTypeError extends NamedError { 34 | public constructor(message: string) { 35 | super('UnexpectedValueTypeError', message); 36 | } 37 | } 38 | 39 | /** 40 | * Indicates an error while performing a JSON clone operation. 41 | */ 42 | export class JsonCloneError extends NamedError { 43 | public constructor(cause: Error) { 44 | super('JsonCloneError', cause.message); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/experimental/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * A staging area for either introducing or removing type and functions, incrementally. 10 | */ 11 | 12 | import { definiteEntriesOf, get, has, isFunction, isString } from '../narrowing'; 13 | import { AnyConstructor, Dictionary, Nullable, Optional, View } from '../types'; 14 | 15 | /** 16 | * @ignore 17 | */ 18 | export type PrimitiveType = 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'; 19 | 20 | /** 21 | * @ignore 22 | */ 23 | export type VerifiableType = PrimitiveType | AnyConstructor; 24 | 25 | /** 26 | * @ignore 27 | */ 28 | export type PropertyShape = { type: VerifiableType; optional: boolean }; 29 | 30 | /** 31 | * @ignore 32 | */ 33 | export type ObjectShape = Dictionary; 34 | 35 | /** 36 | * @ignore 37 | */ 38 | export function is(obj: Nullable, shape: ObjectShape): obj is T { 39 | const isVerifiable = (v: VerifiableType | PropertyShape): v is VerifiableType => isString(v) || isFunction(v); 40 | return ( 41 | !obj || 42 | definiteEntriesOf(shape) 43 | .map(([k, v]) => ({ 44 | key: k, 45 | ...(isVerifiable(v) ? { type: v, optional: false } : v), 46 | })) 47 | .every( 48 | ({ key, type, optional }) => 49 | (optional && !(key in obj)) || 50 | (isString(type) ? typeof get(obj, key) === type : get(obj, key) instanceof type) 51 | ) 52 | ); 53 | } 54 | 55 | /** 56 | * @ignore 57 | */ 58 | // type Foo = { name: string, bar: Bar }; 59 | // class Bar { public baz = 'bar'; } 60 | // const maybeFoo: object = { name: 'bar', bar: new Bar() }; 61 | // const foo = ensure(as(maybeFoo, { name: 'string', bar: Bar })); 62 | export function as(obj: Nullable, shape: ObjectShape): Optional { 63 | return is(obj, shape) ? obj : undefined; 64 | } 65 | 66 | /** 67 | * @ignore 68 | */ 69 | export function hasNull(value: T, key: K): value is T & View { 70 | return has(value, key) && value[key] == null; 71 | } 72 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | export * from './narrowing'; 9 | export * from './types'; 10 | -------------------------------------------------------------------------------- /src/narrowing/as.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, Optional } from '../types'; 9 | import { 10 | isArray, 11 | isBoolean, 12 | isDictionary, 13 | isFunction, 14 | isInstance, 15 | isJsonArray, 16 | isJsonMap, 17 | isNumber, 18 | isObject, 19 | isPlainObject, 20 | isString, 21 | } from './is'; 22 | 23 | /** 24 | * Narrows an `unknown` value to a `string` if it is type-compatible, or returns `undefined` otherwise. 25 | * 26 | * @param value The value to test. 27 | */ 28 | export function asString(value: unknown): Optional; 29 | /** 30 | * Narrows an `unknown` value to a `string` if it is type-compatible, or returns the provided default otherwise. 31 | * 32 | * @param value The value to test. 33 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 34 | */ 35 | export function asString(value: unknown, defaultValue: string): string; 36 | // underlying function 37 | export function asString(value: unknown, defaultValue?: string): Optional { 38 | return isString(value) ? value : defaultValue; 39 | } 40 | 41 | /** 42 | * Narrows an `unknown` value to a `number` if it is type-compatible, or returns `undefined` otherwise. 43 | * 44 | * @param value The value to test. 45 | */ 46 | export function asNumber(value: unknown): Optional; 47 | /** 48 | * Narrows an `unknown` value to a `number` if it is type-compatible, or returns the provided default otherwise. 49 | * 50 | * @param value The value to test. 51 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 52 | */ 53 | export function asNumber(value: unknown, defaultValue: number): number; 54 | // underlying function 55 | export function asNumber(value: unknown, defaultValue?: number): Optional { 56 | return isNumber(value) ? value : defaultValue; 57 | } 58 | 59 | /** 60 | * Narrows an `unknown` value to a `boolean` if it is type-compatible, or returns `undefined` otherwise. 61 | * 62 | * @param value The value to test. 63 | */ 64 | export function asBoolean(value: unknown): Optional; 65 | /** 66 | * Narrows an `unknown` value to a `boolean` if it is type-compatible, or returns the provided default otherwise. 67 | * 68 | * @param value The value to test. 69 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 70 | */ 71 | export function asBoolean(value: unknown, defaultValue: boolean): boolean; 72 | // underlying function 73 | export function asBoolean(value: unknown, defaultValue?: boolean): Optional { 74 | return isBoolean(value) ? value : defaultValue; 75 | } 76 | 77 | /** 78 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns `undefined` otherwise. 79 | * 80 | * @param value The value to test. 81 | */ 82 | export function asObject(value: unknown): Optional; 83 | /** 84 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 85 | * 86 | * @param value The value to test. 87 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 88 | */ 89 | export function asObject(value: unknown, defaultValue: T): T; 90 | // underlying function 91 | export function asObject(value: unknown, defaultValue?: T): Optional { 92 | return isObject(value) ? value : defaultValue; 93 | } 94 | 95 | /** 96 | * Narrows an `unknown` value to a plain `object` if it is type-compatible, or returns `undefined` otherwise. 97 | * 98 | * @param value The value to test. 99 | */ 100 | export function asPlainObject(value: unknown): Optional; 101 | /** 102 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 103 | * 104 | * @param value The value to test. 105 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 106 | */ 107 | export function asPlainObject(value: unknown, defaultValue: T): T; 108 | // underlying function 109 | export function asPlainObject(value: unknown, defaultValue?: T): Optional { 110 | return isPlainObject(value) ? value : defaultValue; 111 | } 112 | 113 | /** 114 | * Narrows an `unknown` value to a `Dictionary` if it is type-compatible, or returns `undefined` otherwise. 115 | * 116 | * @param value The value to test. 117 | */ 118 | export function asDictionary(value: unknown): Optional>; 119 | /** 120 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 121 | * 122 | * @param value The value to test. 123 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 124 | */ 125 | export function asDictionary(value: unknown, defaultValue: Dictionary): Dictionary; 126 | // underlying function 127 | export function asDictionary(value: unknown, defaultValue?: Dictionary): Optional> { 128 | return isDictionary(value) ? value : defaultValue; 129 | } 130 | 131 | /** 132 | * Narrows an `unknown` value to an instance of constructor type `T` if it is type-compatible, or returns `undefined` 133 | * otherwise. 134 | * 135 | * @param value The value to test. 136 | */ 137 | export function asInstance(value: unknown, ctor: C): Optional>; 138 | /** 139 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 140 | * 141 | * @param value The value to test. 142 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 143 | */ 144 | export function asInstance( 145 | value: unknown, 146 | ctor: C, 147 | defaultValue: InstanceType 148 | ): InstanceType; 149 | // underlying function 150 | export function asInstance( 151 | value: unknown, 152 | ctor: C, 153 | defaultValue?: InstanceType 154 | ): Optional> { 155 | return isInstance(value, ctor) ? value : defaultValue; 156 | } 157 | 158 | /** 159 | * Narrows an `unknown` value to an `Array` if it is type-compatible, or returns `undefined` otherwise. 160 | * 161 | * @param value The value to test. 162 | */ 163 | export function asArray(value: unknown): Optional; 164 | /** 165 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 166 | * 167 | * @param value The value to test. 168 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 169 | */ 170 | export function asArray(value: unknown, defaultValue: T[]): T[]; 171 | // underlying function 172 | export function asArray(value: unknown, defaultValue?: T[]): Optional { 173 | return isArray(value) ? value : defaultValue; 174 | } 175 | 176 | /** 177 | * Narrows an `unknown` value to an `AnyFunction` if it is type-compatible, or returns `undefined` otherwise. 178 | * 179 | * @param value The value to test. 180 | */ 181 | export function asFunction(value: unknown): Optional; 182 | /** 183 | * Narrows an `unknown` value to an `object` if it is type-compatible, or returns the provided default otherwise. 184 | * 185 | * @param value The value to test. 186 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 187 | */ 188 | export function asFunction(value: unknown, defaultValue: AnyFunction): AnyFunction; 189 | // underlying function 190 | export function asFunction(value: unknown, defaultValue?: AnyFunction): Optional { 191 | return isFunction(value) ? value : defaultValue; 192 | } 193 | 194 | /** 195 | * Narrows an `AnyJson` value to a `JsonMap` if it is type-compatible, or returns `undefined` otherwise. 196 | * 197 | * @param value The value to test. 198 | */ 199 | export function asJsonMap(value: Optional): Optional; 200 | /** 201 | * Narrows an `AnyJson` value to a `JsonMap` if it is type-compatible, or returns the provided default otherwise. 202 | * 203 | * @param value The value to test. 204 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 205 | */ 206 | export function asJsonMap(value: Optional, defaultValue: JsonMap): JsonMap; 207 | // underlying function 208 | export function asJsonMap(value: Optional, defaultValue?: JsonMap): Optional { 209 | return isJsonMap(value) ? value : defaultValue; 210 | } 211 | 212 | /** 213 | * Narrows an `AnyJson` value to a `JsonArray` if it is type-compatible, or returns `undefined` otherwise. 214 | * 215 | * @param value The value to test. 216 | */ 217 | export function asJsonArray(value: Optional): Optional; 218 | /** 219 | * Narrows an `AnyJson` value to a `JsonArray` if it is type-compatible, or returns the provided default otherwise. 220 | * 221 | * @param value The value to test. 222 | * @param defaultValue The default to return if the value was undefined or of the incorrect type. 223 | */ 224 | export function asJsonArray(value: Optional, defaultValue: JsonArray): JsonArray; 225 | // underlying function 226 | export function asJsonArray(value: Optional, defaultValue?: JsonArray): Optional { 227 | return isJsonArray(value) ? value : defaultValue; 228 | } 229 | -------------------------------------------------------------------------------- /src/narrowing/assert.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { AssertionFailedError } from '../errors'; 9 | import { 10 | AnyArray, 11 | AnyConstructor, 12 | AnyFunction, 13 | AnyJson, 14 | JsonArray, 15 | JsonMap, 16 | Nullable, 17 | Optional, 18 | Dictionary, 19 | } from '../types'; 20 | import { 21 | asArray, 22 | asBoolean, 23 | asDictionary, 24 | asFunction, 25 | asInstance, 26 | asJsonArray, 27 | asJsonMap, 28 | asNumber, 29 | asObject, 30 | asPlainObject, 31 | asString, 32 | } from './as'; 33 | import { toAnyJson } from './to'; 34 | 35 | /** 36 | * Asserts that a given `condition` is true, or raises an error otherwise. 37 | * 38 | * @param condition The condition to test. 39 | * @param message The error message to use if the condition is false. 40 | * @throws {@link AssertionFailedError} If the assertion failed. 41 | */ 42 | export function assert(condition: boolean, message?: string): asserts condition { 43 | if (!condition) { 44 | throw new AssertionFailedError(message ?? 'Assertion condition was false'); 45 | } 46 | } 47 | 48 | /** 49 | * Narrows a type `Nullable` to a `T` or raises an error. 50 | * 51 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 52 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 53 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 54 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 55 | * bad practice unless you have performed some other due diligence in proving that the value must be of 56 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 57 | * proofs. 58 | * 59 | * @param value The value to test. 60 | * @param message The error message to use if `value` is `undefined` or `null`. 61 | * @throws {@link AssertionFailedError} If the value was undefined. 62 | */ 63 | export function assertNonNull(value: Nullable, message?: string): asserts value is T { 64 | assert(value != null, message ?? 'Value is not defined'); 65 | } 66 | 67 | /** 68 | * Narrows an `unknown` value to a `string` if it is type-compatible, or raises an error otherwise. 69 | * 70 | * @param value The value to test. 71 | * @param message The error message to use if `value` is not type-compatible. 72 | * @throws {@link AssertionFailedError} If the value was undefined. 73 | */ 74 | export function assertString(value: unknown, message?: string): asserts value is string { 75 | assertNonNull(asString(value), message ?? 'Value is not a string'); 76 | } 77 | 78 | /** 79 | * Narrows an `unknown` value to a `number` if it is type-compatible, or raises an error otherwise. 80 | * 81 | * @param value The value to test. 82 | * @param message The error message to use if `value` is not type-compatible. 83 | * @throws {@link AssertionFailedError} If the value was undefined. 84 | */ 85 | export function assertNumber(value: unknown, message?: string): asserts value is number { 86 | assertNonNull(asNumber(value), message ?? 'Value is not a number'); 87 | } 88 | 89 | /** 90 | * Narrows an `unknown` value to a `boolean` if it is type-compatible, or raises an error otherwise. 91 | * 92 | * @param value The value to test. 93 | * @param message The error message to use if `value` is not type-compatible. 94 | * @throws {@link AssertionFailedError} If the value was undefined. 95 | */ 96 | export function assertBoolean(value: unknown, message?: string): asserts value is boolean { 97 | assertNonNull(asBoolean(value), message ?? 'Value is not a boolean'); 98 | } 99 | 100 | /** 101 | * Narrows an `unknown` value to an `object` if it is type-compatible, or raises an error otherwise. 102 | * 103 | * @param value The value to test. 104 | * @param message The error message to use if `value` is not type-compatible. 105 | * @throws {@link AssertionFailedError} If the value was undefined. 106 | */ 107 | export function assertObject(value: unknown, message?: string): asserts value is T { 108 | assertNonNull(asObject(value), message ?? 'Value is not an object'); 109 | } 110 | 111 | /** 112 | * Narrows an `unknown` value to an `object` if it is type-compatible and tests positively with {@link isPlainObject}, 113 | * or raises an error otherwise. 114 | * 115 | * @param value The value to test. 116 | * @param message The error message to use if `value` is not type-compatible. 117 | * @throws {@link AssertionFailedError} If the value was undefined. 118 | */ 119 | export function assertPlainObject(value: unknown, message?: string): asserts value is T { 120 | assertNonNull(asPlainObject(value), message ?? 'Value is not a plain object'); 121 | } 122 | 123 | /** 124 | * Narrows an `unknown` value to a `Dictionary` if it is type-compatible and tests positively 125 | * with {@link isDictionary}, or raises an error otherwise. 126 | * 127 | * @param value The value to test. 128 | * @param message The error message to use if `value` is not type-compatible. 129 | * @throws {@link AssertionFailedError} If the value was undefined. 130 | */ 131 | export function assertDictionary(value: unknown, message?: string): asserts value is Dictionary { 132 | assertNonNull(asDictionary(value), message ?? 'Value is not a dictionary object'); 133 | } 134 | 135 | /** 136 | * Narrows an `unknown` value to instance of constructor type `T` if it is type-compatible, or raises an error 137 | * otherwise. 138 | * 139 | * @param value The value to test. 140 | * @param message The error message to use if `value` is not type-compatible. 141 | * @throws {@link AssertionFailedError} If the value was undefined. 142 | */ 143 | export function assertInstance( 144 | value: unknown, 145 | ctor: C, 146 | message?: string 147 | ): asserts value is InstanceType { 148 | assertNonNull(asInstance(value, ctor), message ?? `Value is not an instance of ${ctor.name}`); 149 | } 150 | 151 | /** 152 | * Narrows an `unknown` value to an `Array` if it is type-compatible, or raises an error otherwise. 153 | * 154 | * @param value The value to test. 155 | * @param message The error message to use if `value` is not type-compatible. 156 | * @throws {@link AssertionFailedError} If the value was undefined. 157 | */ 158 | export function assertArray(value: unknown, message?: string): asserts value is AnyArray { 159 | assertNonNull(asArray(value), message ?? 'Value is not an array'); 160 | } 161 | 162 | /** 163 | * Narrows an `unknown` value to an `AnyFunction` if it is type-compatible, or raises an error otherwise. 164 | * 165 | * @param value The value to test. 166 | * @param message The error message to use if `value` is not type-compatible. 167 | * @throws {@link AssertionFailedError} If the value was undefined. 168 | */ 169 | export function assertFunction(value: unknown, message?: string): asserts value is AnyFunction { 170 | assertNonNull(asFunction(value), message ?? 'Value is not a function'); 171 | } 172 | 173 | /** 174 | * Narrows an `unknown` value to an `AnyJson` if it is type-compatible, or returns `undefined` otherwise. 175 | * 176 | * See also caveats noted in {@link isAnyJson}. 177 | * 178 | * @param value The value to test. 179 | * @param message The error message to use if `value` is not type-compatible. 180 | * @throws {@link AssertionFailedError} If the value was not a JSON value type. 181 | */ 182 | export function assertAnyJson(value: unknown, message?: string): asserts value is AnyJson { 183 | assertNonNull(toAnyJson(value), message ?? 'Value is not a JSON-compatible value type'); 184 | } 185 | 186 | /** 187 | * Narrows an `AnyJson` value to a `JsonMap` if it is type-compatible, or raises an error otherwise. 188 | * 189 | * @param value The value to test. 190 | * @param message The error message to use if `value` is not type-compatible. 191 | * @throws {@link AssertionFailedError} If the value was undefined. 192 | */ 193 | export function assertJsonMap(value: Optional, message?: string): asserts value is JsonMap { 194 | assertNonNull(asJsonMap(value), message ?? 'Value is not a JsonMap'); 195 | } 196 | 197 | /** 198 | * Narrows an `AnyJson` value to a `JsonArray` if it is type-compatible, or raises an error otherwise. 199 | * 200 | * @param value The value to test. 201 | * @param message The error message to use if `value` is not type-compatible. 202 | * @throws {@link AssertionFailedError} If the value was undefined. 203 | */ 204 | export function assertJsonArray(value: Optional, message?: string): asserts value is JsonArray { 205 | assertNonNull(asJsonArray(value), message ?? 'Value is not a JsonArray'); 206 | } 207 | -------------------------------------------------------------------------------- /src/narrowing/coerce.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { AnyJson, JsonArray, JsonMap, Nullable, Optional } from '../types'; 9 | import { asJsonArray, asJsonMap } from './as'; 10 | import { isAnyJson } from './is'; 11 | 12 | /** 13 | * Narrows an `unknown` value to an `AnyJson` if it is type-compatible\*, or returns `undefined` otherwise. 14 | * 15 | * _* This is not a 100% safe operation -- it will not deeply validate plain object or array structures 16 | * to ensure that they contain only `AnyJson` values. When type-narrowing potential objects or arrays with this 17 | * function, it's the responsibility of the caller to understand the risks of making such a shallow type assertion 18 | * over the `value` data._ 19 | * 20 | * @param value The value to test. 21 | */ 22 | export function coerceAnyJson(value: unknown): Optional; 23 | /** 24 | * Narrows an `unknown` value to an `AnyJson` if it is type-compatible, or returns the provided default otherwise. 25 | * 26 | * @param value The value to test. 27 | * @param defaultValue The default to return if `value` was undefined or of the incorrect type. 28 | */ 29 | export function coerceAnyJson(value: unknown, defaultValue: AnyJson): AnyJson; 30 | // underlying function 31 | export function coerceAnyJson(value: unknown, defaultValue?: AnyJson): Optional { 32 | return isAnyJson(value) ? value : defaultValue; 33 | } 34 | 35 | /** 36 | * Narrows an object of type `T` to a `JsonMap` using a shallow type-compatibility check. Use this when the source of 37 | * the object is known to be JSON-compatible and you want simple type coercion to a `JsonMap`. Use {@link toJsonMap} 38 | * instead when the `value` object _cannot_ be guaranteed to be JSON-compatible and you want an assurance of runtime 39 | * type safety. This is a shortcut for writing `asJsonMap(coerceAnyJson(value))`. 40 | * 41 | * @param value The object to coerce. 42 | */ 43 | export function coerceJsonMap(value: Nullable): Optional; 44 | /** 45 | * Narrows an object of type `T` to a `JsonMap` using a shallow type-compatibility check. Use this when the source of 46 | * the object is known to be JSON-compatible and you want simple type coercion to a `JsonMap`. Use {@link toJsonMap} 47 | * instead when the `value` object _cannot_ be guaranteed to be JSON-compatible and you want an assurance of runtime 48 | * type safety. This is a shortcut for writing `asJsonMap(coerceAnyJson(value)) ?? defaultValue`. 49 | * 50 | * @param value The object to coerce. 51 | * @param defaultValue The default to return if `value` was not defined. 52 | */ 53 | export function coerceJsonMap(value: Nullable, defaultValue: JsonMap): JsonMap; 54 | // underlying function 55 | export function coerceJsonMap(value: Nullable, defaultValue?: JsonMap): Optional { 56 | return asJsonMap(coerceAnyJson(value)) ?? defaultValue; 57 | } 58 | 59 | /** 60 | * Narrows an array of type `T` to a `JsonArray` using a shallow type-compatibility check. Use this when the source of 61 | * the array is known to be JSON-compatible and you want simple type coercion to a `JsonArray`. Use {@link toJsonArray} 62 | * instead when the `value` array _cannot_ be guaranteed to be JSON-compatible and you want an assurance of runtime 63 | * type safety. This is a shortcut for writing `asJsonArray(coerceAnyJson(value))`. 64 | * 65 | * @param value The array to coerce. 66 | */ 67 | export function coerceJsonArray(value: Nullable): Optional; 68 | /** 69 | * Narrows an array of type `T` to a `JsonArray` using a shallow type-compatibility check. Use this when the source of 70 | * the array is known to be JSON-compatible and you want simple type coercion to a `JsonArray`. Use {@link toJsonArray} 71 | * instead when the `value` array _cannot_ be guaranteed to be JSON-compatible and you want an assurance of runtime 72 | * type safety. This is a shortcut for writing `asJsonArray(coerceAnyJson(value)) ?? defaultValue`. 73 | * 74 | * @param value The array to coerce. 75 | * @param defaultValue The default to return if `value` was not defined. 76 | */ 77 | export function coerceJsonArray(value: Nullable, defaultValue: JsonArray): JsonArray; 78 | // underlying method 79 | export function coerceJsonArray(value: Nullable, defaultValue?: JsonArray): Optional { 80 | return asJsonArray(coerceAnyJson(value)) ?? defaultValue; 81 | } 82 | -------------------------------------------------------------------------------- /src/narrowing/ensure.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { UnexpectedValueTypeError } from '../errors'; 9 | import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, Nullable, Optional } from '../types'; 10 | import { 11 | asArray, 12 | asBoolean, 13 | asDictionary, 14 | asFunction, 15 | asInstance, 16 | asJsonArray, 17 | asJsonMap, 18 | asNumber, 19 | asObject, 20 | asPlainObject, 21 | asString, 22 | } from './as'; 23 | import { toAnyJson } from './to'; 24 | 25 | /** 26 | * Narrows a type `Nullable` to a `T` or raises an error. 27 | * 28 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 29 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 30 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 31 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 32 | * bad practice unless you have performed some other due diligence in proving that the value must be of 33 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 34 | * proofs. 35 | * 36 | * @param value The value to test. 37 | * @param message The error message to use if `value` is `undefined` or `null`. 38 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 39 | */ 40 | export function ensure(value: Nullable, message?: string): T { 41 | if (value == null) { 42 | throw new UnexpectedValueTypeError(message ?? 'Value is not defined'); 43 | } 44 | return value; 45 | } 46 | 47 | /** 48 | * Narrows an `unknown` value to a `string` if it is type-compatible, or raises an error otherwise. 49 | * 50 | * @param value The value to test. 51 | * @param message The error message to use if `value` is not type-compatible. 52 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 53 | */ 54 | export function ensureString(value: unknown, message?: string): string { 55 | return ensure(asString(value), message ?? 'Value is not a string'); 56 | } 57 | 58 | /** 59 | * Narrows an `unknown` value to a `number` if it is type-compatible, or raises an error otherwise. 60 | * 61 | * @param value The value to test. 62 | * @param message The error message to use if `value` is not type-compatible. 63 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 64 | */ 65 | export function ensureNumber(value: unknown, message?: string): number { 66 | return ensure(asNumber(value), message ?? 'Value is not a number'); 67 | } 68 | 69 | /** 70 | * Narrows an `unknown` value to a `boolean` if it is type-compatible, or raises an error otherwise. 71 | * 72 | * @param value The value to test. 73 | * @param message The error message to use if `value` is not type-compatible. 74 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 75 | */ 76 | export function ensureBoolean(value: unknown, message?: string): boolean { 77 | return ensure(asBoolean(value), message ?? 'Value is not a boolean'); 78 | } 79 | 80 | /** 81 | * Narrows an `unknown` value to an `object` if it is type-compatible, or raises an error otherwise. 82 | * 83 | * @param value The value to test. 84 | * @param message The error message to use if `value` is not type-compatible. 85 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 86 | */ 87 | export function ensureObject(value: unknown, message?: string): T { 88 | return ensure(asObject(value), message ?? 'Value is not an object'); 89 | } 90 | 91 | /** 92 | * Narrows an `unknown` value to an `object` if it is type-compatible and tests positively with {@link isPlainObject}, 93 | * or raises an error otherwise. 94 | * 95 | * @param value The value to test. 96 | * @param message The error message to use if `value` is not type-compatible. 97 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 98 | */ 99 | export function ensurePlainObject(value: unknown, message?: string): T { 100 | return ensure(asPlainObject(value), message ?? 'Value is not a plain object'); 101 | } 102 | 103 | /** 104 | * Narrows an `unknown` value to a `Dictionary` if it is type-compatible and tests positively 105 | * with {@link isDictionary}, or raises an error otherwise. 106 | * 107 | * @param value The value to test. 108 | * @param message The error message to use if `value` is not type-compatible. 109 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 110 | */ 111 | export function ensureDictionary(value: unknown, message?: string): Dictionary { 112 | return ensure(asDictionary(value), message ?? 'Value is not a dictionary object'); 113 | } 114 | 115 | /** 116 | * Narrows an `unknown` value to instance of constructor type `T` if it is type-compatible, or raises an error 117 | * otherwise. 118 | * 119 | * @param value The value to test. 120 | * @param message The error message to use if `value` is not type-compatible. 121 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 122 | */ 123 | export function ensureInstance(value: unknown, ctor: C, message?: string): InstanceType { 124 | return ensure(asInstance(value, ctor), message ?? `Value is not an instance of ${ctor.name}`); 125 | } 126 | 127 | /** 128 | * Narrows an `unknown` value to an `Array` if it is type-compatible, or raises an error otherwise. 129 | * 130 | * @param value The value to test. 131 | * @param message The error message to use if `value` is not type-compatible. 132 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 133 | */ 134 | export function ensureArray(value: unknown, message?: string): T[] { 135 | return ensure(asArray(value), message ?? 'Value is not an array'); 136 | } 137 | 138 | /** 139 | * Narrows an `unknown` value to an `AnyFunction` if it is type-compatible, or raises an error otherwise. 140 | * 141 | * @param value The value to test. 142 | * @param message The error message to use if `value` is not type-compatible. 143 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 144 | */ 145 | export function ensureFunction(value: unknown, message?: string): AnyFunction { 146 | return ensure(asFunction(value), message ?? 'Value is not a function'); 147 | } 148 | 149 | /** 150 | * Narrows an `unknown` value to an `AnyJson` if it is type-compatible, or returns `undefined` otherwise. 151 | * 152 | * See also caveats noted in {@link isAnyJson}. 153 | * 154 | * @param value The value to test. 155 | * @param message The error message to use if `value` is not type-compatible. 156 | * @throws {@link UnexpectedValueTypeError} If the value was not a JSON value type. 157 | */ 158 | export function ensureAnyJson(value: unknown, message?: string): AnyJson { 159 | return ensure(toAnyJson(value), message ?? 'Value is not a JSON-compatible value type'); 160 | } 161 | 162 | /** 163 | * Narrows an `AnyJson` value to a `JsonMap` if it is type-compatible, or raises an error otherwise. 164 | * 165 | * @param value The value to test. 166 | * @param message The error message to use if `value` is not type-compatible. 167 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 168 | */ 169 | export function ensureJsonMap(value: Optional, message?: string): JsonMap { 170 | return ensure(asJsonMap(value), message ?? 'Value is not a JsonMap'); 171 | } 172 | 173 | /** 174 | * Narrows an `AnyJson` value to a `JsonArray` if it is type-compatible, or raises an error otherwise. 175 | * 176 | * @param value The value to test. 177 | * @param message The error message to use if `value` is not type-compatible. 178 | * @throws {@link UnexpectedValueTypeError} If the value was undefined. 179 | */ 180 | export function ensureJsonArray(value: Optional, message?: string): JsonArray { 181 | return ensure(asJsonArray(value), message ?? 'Value is not a JsonArray'); 182 | } 183 | -------------------------------------------------------------------------------- /src/narrowing/get.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { 9 | AnyArray, 10 | AnyConstructor, 11 | AnyFunction, 12 | AnyJson, 13 | JsonArray, 14 | JsonMap, 15 | Nullable, 16 | Optional, 17 | Dictionary, 18 | } from '../types'; 19 | import { 20 | asArray, 21 | asBoolean, 22 | asFunction, 23 | asInstance, 24 | asJsonArray, 25 | asJsonMap, 26 | asNumber, 27 | asObject, 28 | asPlainObject, 29 | asString, 30 | asDictionary, 31 | } from './as'; 32 | import { coerceAnyJson } from './coerce'; 33 | import { has } from './has'; 34 | import { valueOrDefault } from './internal'; 35 | 36 | /** 37 | * Given a deep-search query path, returns an object property or array value of an object or array. 38 | * 39 | * ``` 40 | * const obj = { foo: { bar: ['baz'] } }; 41 | * const value = get(obj, 'foo.bar[0]'); 42 | * // type of value -> unknown; value === 'baz' 43 | * 44 | * const value = get(obj, 'foo.bar.nothing', 'default'); 45 | * // type of value -> unknown; value === 'default' 46 | * 47 | * const value = get(obj, 'foo["bar"][0]'); 48 | * // type of value -> unknown; value === 'baz' 49 | * 50 | * const arr = [obj]; 51 | * const value = get(arr, '[0].foo.bar[0]'); 52 | * // type of value -> unknown; value === 'baz' 53 | * ``` 54 | * 55 | * @param from Any value to query. 56 | * @param path The query path. 57 | * @param defaultValue The default to return if the query result was not defined. 58 | */ 59 | export function get(from: unknown, path: string, defaultValue?: unknown): unknown { 60 | return valueOrDefault( 61 | path 62 | // keep values in quotes together 63 | .split(/['"]/) 64 | // values in quotes will always be odd indexes, split the non-quotes values as normal 65 | .reduce( 66 | (r: string[], p: string, index: number) => (index % 2 === 1 ? [...r, p] : [...r, ...p.split(/[.[\]]/)]), 67 | [] 68 | ) 69 | .filter((p) => !!p) 70 | .reduce((r: unknown, p: string) => (has(r, p) ? r[p] : undefined), from), 71 | defaultValue 72 | ); 73 | } 74 | 75 | /** 76 | * Given a deep-search query path, returns an object property or array value of an object or array as a `string`, or 77 | * `undefined` if a value was not found or was not type-compatible. 78 | * 79 | * ``` 80 | * const obj = { foo: { bar: ['baz'] } }; 81 | * const value = getString(obj, 'foo.bar[0]'); 82 | * // type of value -> string; value -> 'baz' 83 | * ``` 84 | * 85 | * @param from Any value to query. 86 | * @param path The query path. 87 | */ 88 | export function getString(from: unknown, path: string): Nullable; 89 | /** 90 | * Given a deep-search query path, returns an object property or array value of an object or array as a `string`, or 91 | * `undefined` if a value was not found or was not type-compatible. 92 | * 93 | * ``` 94 | * const obj = { foo: { bar: ['baz'] } }; 95 | * const value = getString(obj, 'foo.bar[1]', 'default'); 96 | * // type of value -> string; value -> 'default' 97 | * ``` 98 | * 99 | * @param from Any value to query. 100 | * @param path The query path. 101 | * @param defaultValue The default to return if the query result was not defined. 102 | */ 103 | export function getString(from: unknown, path: string, defaultValue: string): string; 104 | // underlying function 105 | export function getString(from: unknown, path: string, defaultValue?: string): Nullable { 106 | return valueOrDefault(asString(get(from, path)), defaultValue); 107 | } 108 | 109 | /** 110 | * Given a deep-search query path, returns an object property or array value of an object or array as a `number`, or 111 | * `undefined` if a value was not found or was not type-compatible. 112 | * 113 | * ``` 114 | * const obj = { foo: { bar: [1] } }; 115 | * const value = getNumber(obj, 'foo.bar[0]'); 116 | * // type of value -> number; value -> 1 117 | * ``` 118 | * 119 | * @param from Any value to query. 120 | * @param path The query path. 121 | */ 122 | export function getNumber(from: unknown, path: string): Nullable; 123 | /** 124 | * Given a deep-search query path, returns an object property or array value of an object or array as a `number`, or 125 | * `undefined` if a value was not found or was not type-compatible. 126 | * 127 | * ``` 128 | * const obj = { foo: { bar: [1] } }; 129 | * const value = getNumber(obj, 'foo.bar[1]', 2); 130 | * // type of value -> number; value -> 2 131 | * ``` 132 | * 133 | * @param from Any value to query. 134 | * @param path The query path. 135 | * @param defaultValue The default to return if the query result was not defined. 136 | */ 137 | export function getNumber(from: unknown, path: string, defaultValue: number): number; 138 | // underlying function 139 | export function getNumber(from: unknown, path: string, defaultValue?: number): Nullable { 140 | return valueOrDefault(asNumber(get(from, path)), defaultValue); 141 | } 142 | 143 | /** 144 | * Given a deep-search query path, returns an object property or array value of an object or array as a `boolean`, or 145 | * `undefined` if a value was not found or was not type-compatible. 146 | * 147 | * ``` 148 | * const obj = { foo: { bar: [true] } }; 149 | * const value = getBoolean(obj, 'foo.bar[0]'); 150 | * // type of value -> boolean; value -> true 151 | * ``` 152 | * 153 | * @param from Any value to query. 154 | * @param path The query path. 155 | */ 156 | export function getBoolean(from: unknown, path: string): Nullable; 157 | /** 158 | * Given a deep-search query path, returns an object property or array value of an object or array as a `boolean`, or 159 | * `undefined` if a value was not found or was not type-compatible. 160 | * 161 | * ``` 162 | * const obj = { foo: { bar: [true] } }; 163 | * const value = getBoolean(obj, 'foo.bar[1]', false); 164 | * // type of value -> boolean; value -> false 165 | * ``` 166 | * 167 | * @param from Any value to query. 168 | * @param path The query path. 169 | * @param defaultValue The default to return if the query result was not defined. 170 | */ 171 | export function getBoolean(from: unknown, path: string, defaultValue: boolean): boolean; 172 | // underlying function 173 | export function getBoolean(from: unknown, path: string, defaultValue?: boolean): Nullable { 174 | return valueOrDefault(asBoolean(get(from, path)), defaultValue); 175 | } 176 | 177 | /** 178 | * Given a deep-search query path, returns an object property or array value of an object or array as an `object`, or 179 | * `undefined` if a value was not found or was not type-compatible. 180 | * 181 | * ``` 182 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 183 | * const value = getObject(obj, 'foo.bar[0]'); 184 | * // type of value -> object; value -> { name: 'baz' } 185 | * ``` 186 | * 187 | * @param from Any value to query. 188 | * @param path The query path. 189 | */ 190 | export function getObject(from: unknown, path: string): Nullable; 191 | /** 192 | * Given a deep-search query path, returns an object property or array value of an object or array as an `object`, or 193 | * `undefined` if a value was not found or was not type-compatible. 194 | * 195 | * ``` 196 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 197 | * const value = getObject(obj, 'foo.bar[1]', { name: 'buzz' }); 198 | * // type of value -> object; value -> { name: 'buzz' } 199 | * ``` 200 | * 201 | * @param from Any value to query. 202 | * @param path The query path. 203 | * @param defaultValue The default to return if the query result was not defined. 204 | */ 205 | export function getObject(from: unknown, path: string, defaultValue: T): T; 206 | // underlying function 207 | export function getObject(from: unknown, path: string, defaultValue?: T): Nullable { 208 | return valueOrDefault(asObject(get(from, path)), defaultValue); 209 | } 210 | 211 | /** 212 | * Given a deep-search query path, returns an object property or array value of an object or array as an `object`, or 213 | * `undefined` if a value was not found or was not type-compatible. This differs from {@link getObject} by way of 214 | * testing for the property value type compatibility using {@link isPlainObject} instead of {@link isObject}. 215 | * 216 | * ``` 217 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 218 | * const value = getPlainObject(obj, 'foo.bar[0]'); 219 | * // type of value -> object; value -> { name: 'baz' } 220 | * ``` 221 | * 222 | * @param from Any value to query. 223 | * @param path The query path. 224 | */ 225 | export function getPlainObject(from: unknown, path: string): Nullable; 226 | /** 227 | * Given a deep-search query path, returns an object property or array value of an object or array as an `object`, or 228 | * `undefined` if a value was not found or was not type-compatible. This differs from {@link getObject} by way of 229 | * testing for the property value type compatibility using {@link isPlainObject} instead of {@link isObject}. 230 | * 231 | * ``` 232 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 233 | * const value = getPlainObject(obj, 'foo.bar[1]', { name: 'buzz' }); 234 | * // type of value -> object; value -> { name: 'buzz' } 235 | * ``` 236 | * 237 | * @param from Any value to query. 238 | * @param path The query path. 239 | * @param defaultValue The default to return if the query result was not defined. 240 | */ 241 | export function getPlainObject(from: unknown, path: string, defaultValue: T): T; 242 | // underlying function 243 | export function getPlainObject(from: unknown, path: string, defaultValue?: T): Nullable { 244 | return valueOrDefault(asPlainObject(get(from, path)), defaultValue); 245 | } 246 | 247 | /** 248 | * Given a deep-search query path, returns an object property or array value of an object or array as a `Dictionary`, or 249 | * `undefined` if a value was not found or was not type-compatible. 250 | * 251 | * ``` 252 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 253 | * const value = getDictionary(obj, 'foo.bar[0]'); 254 | * // type of value -> Dictionary; value -> { name: 'baz' } 255 | * ``` 256 | * 257 | * @param from Any value to query. 258 | * @param path The query path. 259 | */ 260 | export function getDictionary(from: unknown, path: string): Nullable>; 261 | /** 262 | * Given a deep-search query path, returns an object property or array value of an object or array as an `Dictionary`, or 263 | * `undefined` if a value was not found or was not type-compatible. 264 | * 265 | * ``` 266 | * const obj = { foo: { bar: [{ name: 'baz' }] } }; 267 | * const value = getDictionary(obj, 'foo.bar[1]', { name: 'buzz' }); 268 | * // type of value -> Dictionary; value -> { name: 'buzz' } 269 | * ``` 270 | * 271 | * @param from Any value to query. 272 | * @param path The query path. 273 | * @param defaultValue The default to return if the query result was not defined. 274 | */ 275 | export function getDictionary(from: unknown, path: string, defaultValue: Dictionary): Dictionary; 276 | // underlying function 277 | export function getDictionary( 278 | from: unknown, 279 | path: string, 280 | defaultValue?: Dictionary 281 | ): Nullable> { 282 | return valueOrDefault(asDictionary(get(from, path)), defaultValue); 283 | } 284 | 285 | /** 286 | * Given a deep-search query path, returns an object property or array value of an object or array as an instance of 287 | * class type `C`, or `undefined` if a value was not found or was not type-compatible. 288 | * 289 | * ``` 290 | * class Example { ... } 291 | * const obj = { foo: { bar: [new Example()] } }; 292 | * const value = getInstance(obj, 'foo.bar[0]', Example); 293 | * // type of value -> Example 294 | * ``` 295 | * 296 | * @param from Any value to query. 297 | * @param path The query path. 298 | */ 299 | export function getInstance(from: unknown, path: string, ctor: C): Nullable>; 300 | /** 301 | * Given a deep-search query path, returns an object property or array value of an object or array as an instance of 302 | * class type `C`, or `undefined` if a value was not found or was not type-compatible. 303 | * 304 | * ``` 305 | * class Example { ... } 306 | * const obj = { foo: { bar: [new Example()] } }; 307 | * const value = getInstance(obj, 'foo.bar[0]', Example); 308 | * // type of value -> Example; value -> new Example() 309 | * ``` 310 | * 311 | * @param from Any value to query. 312 | * @param path The query path. 313 | * @param defaultValue The default to return if the query result was not defined. 314 | */ 315 | export function getInstance( 316 | from: unknown, 317 | path: string, 318 | ctor: C, 319 | defaultValue: InstanceType 320 | ): InstanceType; 321 | // underlying function 322 | export function getInstance( 323 | from: unknown, 324 | path: string, 325 | ctor: C, 326 | defaultValue?: InstanceType 327 | ): Nullable> { 328 | return valueOrDefault(asInstance(get(from, path), ctor), defaultValue); 329 | } 330 | 331 | /** 332 | * Given a deep-search query path, returns an object property or array value of an object or array as an 333 | * {@link AnyArray}, or `undefined` if a value was not found or was not type-compatible. 334 | * 335 | * ``` 336 | * const obj = { foo: { bar: [1, 2, 3] } }; 337 | * const value = getArray(obj, 'foo.bar'); 338 | * // type of value -> AnyArray; value -> [1, 2, 3] 339 | * ``` 340 | * 341 | * @param from Any value to query. 342 | * @param path The query path. 343 | */ 344 | export function getArray(from: unknown, path: string): Nullable; 345 | /** 346 | * Given a deep-search query path, returns an object property or array value of an object or array as an 347 | * {@link AnyArray}, or `undefined` if a value was not found or was not type-compatible. 348 | * 349 | * ``` 350 | * const obj = { foo: { bar: [1, 2, 3] } }; 351 | * const value = getArray(obj, 'foo.baz', [4, 5, 6]); 352 | * // type of value -> AnyArray; value -> [4, 5, 6] 353 | * ``` 354 | * 355 | * @param from Any value to query. 356 | * @param path The query path. 357 | * @param defaultValue The default to return if the query result was not defined. 358 | */ 359 | export function getArray(from: unknown, path: string, defaultValue: AnyArray): AnyArray; 360 | // underlying function 361 | export function getArray(from: unknown, path: string, defaultValue?: AnyArray): Nullable { 362 | return valueOrDefault(asArray(get(from, path)), defaultValue); 363 | } 364 | 365 | /** 366 | * Given a deep-search query path, returns an object property or array value of an object or array as an 367 | * {@link AnyFunction}, or `undefined` if a value was not found or was not type-compatible. 368 | * 369 | * ``` 370 | * const obj = { foo: { bar: [(arg: string) => `Hi, ${arg}`] } }; 371 | * const value = getFunction(obj, 'foo.bar[0]'); 372 | * // type of value -> AnyArray; value -> (arg: string) => `Hi, ${arg}` 373 | * ``` 374 | * 375 | * @param from Any value to query. 376 | * @param path The query path. 377 | */ 378 | export function getFunction(from: unknown, path: string): Nullable; 379 | /** 380 | * Given a deep-search query path, returns an object property or array value of an object or array as an 381 | * {@link AnyFunction}, or `undefined` if a value was not found or was not type-compatible. 382 | * 383 | * ``` 384 | * const obj = { foo: { bar: [(arg: string) => `Hi, ${arg}`] } }; 385 | * const value = getFunction(obj, 'foo.bar[1]', (arg: string) => `Bye, ${arg}`); 386 | * // type of value -> AnyArray; value -> (arg: string) => `Bye, ${arg}`) 387 | * ``` 388 | * 389 | * @param from Any value to query. 390 | * @param path The query path. 391 | * @param defaultValue The default to return if the query result was not defined. 392 | */ 393 | export function getFunction(from: unknown, path: string, defaultValue: AnyFunction): AnyFunction; 394 | // underlying function 395 | export function getFunction(from: unknown, path: string, defaultValue?: AnyFunction): Nullable { 396 | return valueOrDefault(asFunction(get(from, path)), defaultValue); 397 | } 398 | 399 | /** 400 | * Given a deep-search query path, returns an object property or array value of a {@link JsonCollection} as an 401 | * {@link AnyJson}, or `undefined` if a value was not found or was not type-compatible. 402 | * 403 | * See {@link coerceAnyJson} for caveats regarding shallow type detection of `AnyJson` values from untyped sources. 404 | * 405 | * ``` 406 | * const obj = { foo: { bar: [{ a: 'b' }] } }; 407 | * const value = getAnyJson(obj, 'foo.bar[0]'); 408 | * // type of value -> AnyJson; value -> { a: 'b' } 409 | * ``` 410 | * 411 | * @param from The JSON value to query. 412 | * @param path The query path. 413 | */ 414 | export function getAnyJson(from: Optional, path: string): Optional; 415 | /** 416 | * Given a deep-search query path, returns an object property or array value of a {@link JsonCollection} as an 417 | * {@link AnyJson}, or the given default if a value was not found or was not type-compatible. 418 | * 419 | * ``` 420 | * const obj = { foo: { bar: [{ a: 'b' }] } }; 421 | * const value = getAnyJson(obj, 'foo.bar[1]', { c: 'd' }); 422 | * // type of value -> AnyJson; value -> { c: 'd' } 423 | * ``` 424 | * 425 | * @param from The JSON value to query. 426 | * @param path The query path. 427 | * @param defaultValue The default to return if the query result was not defined. 428 | */ 429 | export function getAnyJson(from: Optional, path: string, defaultValue: AnyJson): AnyJson; 430 | // underlying function 431 | export function getAnyJson(from: Optional, path: string, defaultValue?: AnyJson): Optional { 432 | return valueOrDefault(coerceAnyJson(get(from, path)), defaultValue); 433 | } 434 | 435 | /** 436 | * Given a deep-search query path, returns an object property or array value from an {@link AnyJson} as a 437 | * {@link JsonMap}, or `undefined` if a value was not found or was not type-compatible. 438 | * 439 | * ``` 440 | * const obj = { foo: { bar: [{ a: 'b' }] } }; 441 | * const value = getJsonMap(obj, 'foo.bar[0]'); 442 | * // type of value -> JsonMap; value -> { a: 'b' } 443 | * ``` 444 | * 445 | * @param from The JSON value to query. 446 | * @param path The query path. 447 | */ 448 | export function getJsonMap(from: Optional, path: string): Nullable; 449 | /** 450 | * Given a deep-search query path, returns an object property or array value from an {@link AnyJson} as a 451 | * {@link JsonMap}, or the given default if a value was not found or was not type-compatible. 452 | * 453 | * ``` 454 | * const obj = { foo: { bar: [{ a: 'b' }] } }; 455 | * const value = getJsonMap(obj, 'foo.bar[1]', { c: 'd' }); 456 | * // type of value -> JsonMap; value -> { c: 'd' } 457 | * ``` 458 | * 459 | * @param from The JSON value to query. 460 | * @param path The query path. 461 | * @param defaultValue The default to return if the query result was not defined. 462 | */ 463 | export function getJsonMap(from: Optional, path: string, defaultValue: JsonMap): JsonMap; 464 | // underlying function 465 | export function getJsonMap(from: Optional, path: string, defaultValue?: JsonMap): Nullable { 466 | return valueOrDefault(asJsonMap(getAnyJson(from, path)), defaultValue); 467 | } 468 | 469 | /** 470 | * Given a deep-search query path, returns an object property or array value from an {@link AnyJson} as a 471 | * {@link JsonArray}, or `undefined` if a value was not found or was not type-compatible. 472 | * 473 | * ``` 474 | * const obj = { foo: { bar: [1, 2, 3] } }; 475 | * const value = getJsonArray(obj, 'foo.bar'); 476 | * // type of value -> JsonArray; value -> [1, 2, 3] 477 | * ``` 478 | * 479 | * @param from The JSON value to query. 480 | * @param path The query path. 481 | */ 482 | export function getJsonArray(from: Optional, path: string): Nullable; 483 | /** 484 | * Given a deep-search query path, returns an object property or array value from an {@link AnyJson} as a 485 | * {@link JsonArray}, or the given default if a value was not found or was not type-compatible. 486 | * 487 | * ``` 488 | * const obj = { foo: { bar: [1, 2, 3] } }; 489 | * const value = getJsonArray(obj, 'foo.baz', [4, 5, 6]); 490 | * // type of value -> JsonArray; value -> [4, 5, 6] 491 | * ``` 492 | * 493 | * @param from The JSON value to query. 494 | * @param path The query path. 495 | * @param defaultValue The default to return if the query result was not defined. 496 | */ 497 | export function getJsonArray(from: Optional, path: string, defaultValue: JsonArray): JsonArray; 498 | // underlying function 499 | export function getJsonArray(from: Optional, path: string, defaultValue?: JsonArray): Nullable { 500 | return valueOrDefault(asJsonArray(getAnyJson(from, path)), defaultValue); 501 | } 502 | -------------------------------------------------------------------------------- /src/narrowing/has.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { 9 | AnyArray, 10 | AnyConstructor, 11 | AnyFunction, 12 | AnyJson, 13 | JsonArray, 14 | JsonMap, 15 | Many, 16 | Optional, 17 | View, 18 | Dictionary, 19 | } from '../types'; 20 | import { 21 | isAnyJson, 22 | isArray, 23 | isBoolean, 24 | isFunction, 25 | isJsonArray, 26 | isJsonMap, 27 | isNumber, 28 | isObject, 29 | isPlainObject, 30 | isString, 31 | isDictionary, 32 | } from './is'; 33 | 34 | /** 35 | * Tests whether a value of type `T` contains one or more property `keys`. If so, the type of the tested value is 36 | * narrowed to reflect the existence of those keys for convenient access in the same scope. Returns false if the 37 | * property key does not exist on the target type, which must be an object. Returns true if the property key exists, 38 | * even if the associated value is `undefined` or `null`. 39 | * 40 | * ``` 41 | * // type of obj -> unknown 42 | * if (has(obj, 'name')) { 43 | * // type of obj -> { name: unknown } 44 | * if (has(obj, 'data')) { 45 | * // type of obj -> { name: unknown } & { data: unknown } 46 | * } else if (has(obj, ['error', 'status'])) { 47 | * // type of obj -> { name: unknown } & { error: unknown, status: unknown } 48 | * } 49 | * } 50 | * ``` 51 | * 52 | * @param value The value to test. 53 | * @param keys One or more `string` keys to check for existence. 54 | */ 55 | export function has(value: T, keys: Many): value is T & object & View { 56 | return isObject(value) && (isArray(keys) ? keys.every((k) => k in value) : keys in value); 57 | } 58 | 59 | /** 60 | * Tests whether a value of type `T` contains a property `key` of type `string`. If so, the type of the tested value is 61 | * narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if the 62 | * property key does not exist on the object or the value stored by that key is not of type `string`. 63 | * 64 | * ``` 65 | * // type of obj -> unknown 66 | * if (hasString(obj, 'name')) { 67 | * // type of obj -> { name: string } 68 | * if (hasString(obj, 'message')) { 69 | * // type of obj -> { name: string } & { message: string } 70 | * } 71 | * } 72 | * ``` 73 | * 74 | * @param value The value to test. 75 | * @param keys A `string` key to check for existence. 76 | */ 77 | export function hasString(value: T, key: K): value is T & object & View { 78 | return has(value, key) && isString(value[key]); 79 | } 80 | 81 | /** 82 | * Tests whether a value of type `T` contains a property `key` of type `number`. If so, the type of the tested value is 83 | * narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if the 84 | * property key does not exist on the object or the value stored by that key is not of type `number`. 85 | * 86 | * ``` 87 | * // type of obj -> unknown 88 | * if (hasNumber(obj, 'offset')) { 89 | * // type of obj -> { offset: number } 90 | * if (hasNumber(obj, 'page') && hasArray(obj, 'items')) { 91 | * // type of obj -> { offset: number } & { page: number } & { items: unknown[] } 92 | * } 93 | * } 94 | * ``` 95 | * 96 | * @param value The value to test. 97 | * @param keys A `number` key to check for existence. 98 | */ 99 | export function hasNumber(value: T, key: K): value is T & object & View { 100 | return has(value, key) && isNumber(value[key]); 101 | } 102 | 103 | /** 104 | * Tests whether a value of type `T` contains a property `key` of type `boolean`. If so, the type of the tested value is 105 | * narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if the 106 | * property key does not exist on the object or the value stored by that key is not of type `boolean`. 107 | * 108 | * ``` 109 | * // type of obj -> unknown 110 | * if (hasBoolean(obj, 'enabled')) { 111 | * // type of obj -> { enabled: boolean } 112 | * if (hasBoolean(obj, 'hidden')) { 113 | * // type of obj -> { enabled: boolean } & { hidden: boolean } 114 | * } 115 | * } 116 | * ``` 117 | * 118 | * @param value The value to test. 119 | * @param keys A `boolean` key to check for existence. 120 | */ 121 | export function hasBoolean(value: T, key: K): value is T & object & View { 122 | return has(value, key) && isBoolean(value[key]); 123 | } 124 | 125 | /** 126 | * Tests whether a value of type `T` contains a property `key` of type `object`. If so, the type of the tested value is 127 | * narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if the 128 | * property key does not exist on the object or the value stored by that key is not of type `object`. 129 | * 130 | * ``` 131 | * // type of obj -> unknown 132 | * if (hasNumber(obj, 'status')) { 133 | * // type of obj -> { status: number } 134 | * if (hasObject(obj, 'data')) { 135 | * // type of obj -> { status: number } & { data: object } 136 | * } else if (hasString('error')) { 137 | * // type of obj -> { status: number } & { error: string } 138 | * } 139 | * } 140 | * ``` 141 | * 142 | * @param value The value to test. 143 | * @param keys An `object` key to check for existence. 144 | */ 145 | export function hasObject( 146 | value: T, 147 | key: K 148 | ): value is T & object & View { 149 | return has(value, key) && isObject(value[key]); 150 | } 151 | 152 | /** 153 | * Tests whether a value of type `T` contains a property `key` whose type tests positively when tested with 154 | * {@link isPlainObject}. If so, the type of the tested value is narrowed to reflect the existence of that key for 155 | * convenient access in the same scope. Returns `false` if the property key does not exist on the object or the value 156 | * stored by that key is not of type `object`. 157 | * 158 | * ``` 159 | * // type of obj -> unknown 160 | * if (hasNumber(obj, 'status')) { 161 | * // type of obj -> { status: number } 162 | * if (hasPlainObject(obj, 'data')) { 163 | * // type of obj -> { status: number } & { data: object } 164 | * } else if (hasString('error')) { 165 | * // type of obj -> { status: number } & { error: string } 166 | * } 167 | * } 168 | * ``` 169 | * 170 | * @param value The value to test. 171 | * @param keys A "plain" `object` key to check for existence. 172 | */ 173 | export function hasPlainObject( 174 | value: T, 175 | key: K 176 | ): value is T & object & View { 177 | return has(value, key) && isPlainObject(value[key]); 178 | } 179 | 180 | /** 181 | * Tests whether a value of type `T` contains a property `key` whose type tests positively when tested with 182 | * {@link isDictionary}. If so, the type of the tested value is narrowed to reflect the existence of that key for 183 | * convenient access in the same scope. Returns `false` if the property key does not exist on the object or the value 184 | * stored by that key is not of type `object`. 185 | * 186 | * ``` 187 | * // type of obj -> unknown 188 | * if (hasNumber(obj, 'status')) { 189 | * // type of obj -> { status: number } 190 | * if (hasDictionary(obj, 'data')) { 191 | * // type of obj -> { status: number } & { data: Dictionary } 192 | * } else if (hasString('error')) { 193 | * // type of obj -> { status: number } & { error: string } 194 | * } 195 | * } 196 | * ``` 197 | * 198 | * @param value The value to test. 199 | * @param keys A "dictionary" `object` key to check for existence. 200 | */ 201 | export function hasDictionary( 202 | value: T, 203 | key: K 204 | ): value is T & object & View> { 205 | return has(value, key) && isDictionary(value[key]); 206 | } 207 | 208 | /** 209 | * Tests whether a value of type `T` contains a property `key` whose type tests positively when tested with 210 | * {@link isInstance} when compared with the given constructor type `C`. If so, the type of the tested value is 211 | * narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if the 212 | * property key does not exist on the object or the value stored by that key is not an instance of `C`. 213 | * 214 | * ``` 215 | * class ServerResponse { ... } 216 | * // type of obj -> unknown 217 | * if (hasNumber(obj, 'status')) { 218 | * // type of obj -> { status: number } 219 | * if (hasInstance(obj, 'data', ServerResponse)) { 220 | * // type of obj -> { status: number } & { data: ServerResponse } 221 | * } else if (hasString('error')) { 222 | * // type of obj -> { status: number } & { error: string } 223 | * } 224 | * } 225 | * ``` 226 | * 227 | * @param value The value to test. 228 | * @param keys An instance of type `C` key to check for existence. 229 | */ 230 | export function hasInstance( 231 | value: Optional, 232 | key: K, 233 | ctor: C 234 | ): value is T & View> { 235 | return has(value, key) && value[key] instanceof ctor; 236 | } 237 | 238 | /** 239 | * Tests whether a value of type `T` contains a property `key` of type {@link AnyArray}. If so, the type of the tested 240 | * value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns `false` if 241 | * the property key does not exist on the object or the value stored by that key is not of type {@link AnyArray}. 242 | * 243 | * ``` 244 | * // type of obj -> unknown 245 | * if (hasNumber(obj, 'offset')) { 246 | * // type of obj -> { offset: number } 247 | * if (hasNumber(obj, 'page') && hasArray(obj, 'items')) { 248 | * // type of obj -> { offset: number } & { page: number } & { items: AnyArray } 249 | * } 250 | * } 251 | * ``` 252 | * 253 | * @param value The value to test. 254 | * @param keys An `AnyArray` key to check for existence. 255 | */ 256 | export function hasArray(value: Optional, key: K): value is T & object & View { 257 | return has(value, key) && isArray(value[key]); 258 | } 259 | 260 | /** 261 | * Tests whether a value of type `T` contains a property `key` of type {@link AnyFunction}. If so, the type of the 262 | * tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns 263 | * `false` if the property key does not exist on the object or the value stored by that key is not of type 264 | * {@link AnyFunction}. 265 | * 266 | * ``` 267 | * // type of obj -> unknown 268 | * if (hasFunction(obj, 'callback')) { 269 | * // type of obj -> { callback: AnyFunction } 270 | * obj.callback(response); 271 | * } 272 | * ``` 273 | * 274 | * @param value The value to test. 275 | * @param keys An `AnyFunction` key to check for existence. 276 | */ 277 | export function hasFunction( 278 | value: Optional, 279 | key: K 280 | ): value is T & object & View { 281 | return has(value, key) && isFunction(value[key]); 282 | } 283 | 284 | /** 285 | * Tests whether a value of type `T` contains a property `key` of type {@link AnyJson}, _using a shallow test for 286 | * `AnyJson` compatibility_ (see {@link isAnyJson} for more information). If so, the type of the 287 | * tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns 288 | * `false` if the property key does not exist on the object or the value stored by that key is not of type 289 | * {@link AnyJson}. 290 | * 291 | * ``` 292 | * // type of obj -> unknown 293 | * if (hasAnyJson(obj, 'body')) { 294 | * // type of obj -> { body: AnyJson } 295 | * } 296 | * ``` 297 | * 298 | * @param value The value to test. 299 | * @param keys An `AnyJson` key to check for existence. 300 | */ 301 | export function hasAnyJson(value: Optional, key: K): value is T & object & View { 302 | return has(value, key) && isAnyJson(value[key]); 303 | } 304 | 305 | /** 306 | * Tests whether a value of type `T extends AnyJson` contains a property `key` of type {@link JsonMap}. If so, the type 307 | * of the tested value is narrowed to reflect the existence of that key for convenient access in the same scope. Returns 308 | * `false` if the property key does not exist on the object or the value stored by that key is not of type 309 | * {@link JsonMap}. 310 | * 311 | * ``` 312 | * // type of obj -> unknown 313 | * if (hasJsonMap(obj, 'body')) { 314 | * // type of obj -> { body: JsonMap } 315 | * } 316 | * ``` 317 | * 318 | * @param value The value to test. 319 | * @param keys A `JsonMap` key to check for existence. 320 | */ 321 | export function hasJsonMap( 322 | value: Optional, 323 | key: K 324 | ): value is T & JsonMap & View { 325 | return hasAnyJson(value, key) && isJsonMap(value[key]); 326 | } 327 | 328 | /** 329 | * Tests whether a value of type `T extends AnyJson` contains a property `key` of type {@link JsonArray}. If so, the 330 | * type of the tested value is narrowed to reflect the existence of that key for convenient access in the same scope. 331 | * Returns `false` if the property key does not exist on the object or the value stored by that key is not of type 332 | * {@link JsonArray}. 333 | * 334 | * ``` 335 | * // type of obj -> unknown 336 | * if (hasJsonArray(obj, 'body')) { 337 | * // type of obj -> { body: JsonArray } 338 | * } 339 | * ``` 340 | * 341 | * @param value The value to test. 342 | * @param keys A `JsonArray` key to check for existence. 343 | */ 344 | export function hasJsonArray( 345 | value: Optional, 346 | key: K 347 | ): value is T & JsonMap & View { 348 | return hasAnyJson(value, key) && isJsonArray(value[key]); 349 | } 350 | -------------------------------------------------------------------------------- /src/narrowing/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | export * from './as'; 9 | export * from './assert'; 10 | export * from './coerce'; 11 | export * from './ensure'; 12 | export * from './get'; 13 | export * from './has'; 14 | export * from './is'; 15 | export * from './object'; 16 | export * from './to'; 17 | -------------------------------------------------------------------------------- /src/narrowing/internal.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { Nullable } from '../types'; 9 | 10 | /** 11 | * Returns the given `value` if not either `undefined` or `null`, or the given `defaultValue` otherwise if defined. 12 | * Returns `null` if the value is `null` and `defaultValue` is `undefined`. 13 | * 14 | * @param value The value to test. 15 | * @param defaultValue The default to return if `value` was not defined. 16 | * @ignore 17 | */ 18 | export function valueOrDefault(value: Nullable, defaultValue: Nullable): Nullable { 19 | return value != null || defaultValue === undefined ? value : defaultValue; 20 | } 21 | -------------------------------------------------------------------------------- /src/narrowing/is.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { AnyConstructor, AnyFunction, AnyJson, Dictionary, JsonArray, JsonMap, KeyOf, Optional, View } from '../types'; 9 | 10 | /** 11 | * Tests whether an `unknown` value is a `string`. 12 | * 13 | * @param value The value to test. 14 | */ 15 | export function isString(value: unknown): value is string { 16 | return typeof value === 'string'; 17 | } 18 | 19 | /** 20 | * Tests whether an `unknown` value is a `number`. 21 | * 22 | * @param value The value to test. 23 | */ 24 | export function isNumber(value: unknown): value is number { 25 | return typeof value === 'number'; 26 | } 27 | 28 | /** 29 | * Tests whether an `unknown` value is a `boolean`. 30 | * 31 | * @param value The value to test. 32 | */ 33 | export function isBoolean(value: unknown): value is boolean { 34 | return typeof value === 'boolean'; 35 | } 36 | 37 | /** 38 | * Tests whether an `unknown` value is an `Object` subtype (e.g., arrays, functions, objects, regexes, 39 | * new Number(0), new String(''), and new Boolean(true)). Tests that wish to distinguish objects that 40 | * were created from literals or that otherwise were not created via a non-`Object` constructor and do 41 | * not have a prototype chain should instead use {@link isPlainObject}. 42 | * 43 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 44 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 45 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 46 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 47 | * bad practice unless you have performed some other due diligence in proving that the value must be of 48 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 49 | * proofs. 50 | * 51 | * @param value The value to test. 52 | */ 53 | export function isObject(value: unknown): value is T { 54 | return value != null && (typeof value === 'object' || typeof value === 'function'); 55 | } 56 | 57 | /** 58 | * Tests whether an `unknown` value is a `function`. 59 | * 60 | * @param value The value to test. 61 | */ 62 | export function isFunction(value: unknown): value is T { 63 | return typeof value === 'function'; 64 | } 65 | 66 | /** 67 | * Tests whether or not an `unknown` value is a plain JavaScript object. That is, if it is an object created 68 | * by the Object constructor or one with a null `prototype`. 69 | * 70 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 71 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 72 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 73 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 74 | * bad practice unless you have performed some other due diligence in proving that the value must be of 75 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 76 | * proofs. 77 | * 78 | * @param value The value to test. 79 | */ 80 | export function isPlainObject(value: unknown): value is T { 81 | const isObjectObject = (o: unknown): o is Dictionary => 82 | isObject(o) && Object.prototype.toString.call(o) === '[object Object]'; 83 | if (!isObjectObject(value)) return false; 84 | const ctor = value.constructor; 85 | if (!isFunction(ctor)) return false; 86 | if (!isObjectObject(ctor.prototype)) return false; 87 | // eslint-disable-next-line no-prototype-builtins 88 | if (!ctor.prototype.hasOwnProperty('isPrototypeOf')) return false; 89 | return true; 90 | } 91 | 92 | /** 93 | * A shortcut for testing the suitability of a value to be used as a `Dictionary` type. Shorthand for 94 | * writing `isPlainObject>(value)`. While some non-plain-object types are compatible with 95 | * index signatures, they were less typically used as such, so this function focuses on the 80% case. 96 | * 97 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 98 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 99 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 100 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 101 | * bad practice unless you have performed some other due diligence in proving that the value must be of 102 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 103 | * proofs. 104 | * 105 | * @param value The value to test. 106 | */ 107 | export function isDictionary(value: unknown): value is Dictionary { 108 | return isPlainObject>(value); 109 | } 110 | 111 | /** 112 | * Tests whether an `unknown` value is a `function`. 113 | * 114 | * @param value The value to test. 115 | */ 116 | export function isInstance(value: unknown, ctor: C): value is InstanceType { 117 | return value instanceof ctor; 118 | } 119 | 120 | /** 121 | * Tests whether an `unknown` value is a class constructor that is either equal to or extends another class 122 | * constructor. 123 | * 124 | * @param value The value to test. 125 | * @param cls The class to test against. 126 | */ 127 | export function isClassAssignableTo(value: unknown, cls: C): value is C { 128 | // avoid circular dependency with has.ts 129 | const has = (v: T, k: K): v is T & View => isObject(v) && k in v; 130 | return value === cls || (has(value, 'prototype') && value.prototype instanceof cls); 131 | } 132 | 133 | /** 134 | * Tests whether an `unknown` value is an `Array`. 135 | * 136 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 137 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 138 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 139 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 140 | * bad practice unless you have performed some other due diligence in proving that the value must be of 141 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 142 | * proofs. 143 | * 144 | * @param value The value to test. 145 | */ 146 | export function isArray(value: unknown): value is T[] { 147 | return Array.isArray(value); 148 | } 149 | 150 | /** 151 | * Tests whether an `unknown` value conforms to {@link AnyArrayLike}. 152 | * 153 | * Use of the type parameter `T` to further narrow the type signature of the value being tested is 154 | * strongly discouraged unless you are completely confident that the value is of the necessary shape to 155 | * conform with `T`. This function does nothing at either compile time or runtime to prove the value is of 156 | * shape `T`, so doing so amounts to nothing more than performing a type assertion, which is generally a 157 | * bad practice unless you have performed some other due diligence in proving that the value must be of 158 | * shape `T`. Use of the functions in the `has` co-library are useful for performing such full or partial 159 | * proofs. 160 | * 161 | * @param value The value to test. 162 | */ 163 | export function isArrayLike(value: unknown): value is ArrayLike { 164 | // avoid circular dependency with has.ts 165 | const hasLength = (v: unknown): v is View<'length', number> => isObject(v) && 'length' in v; 166 | return !isFunction(value) && (isString(value) || hasLength(value)); 167 | } 168 | 169 | /** 170 | * Tests whether `unknown` value is a valid JSON type. Note that objects and arrays are only checked using a shallow 171 | * test. To be sure that a given value is JSON-compatible at runtime, see {@link toAnyJson}. 172 | * 173 | * @param value The value to test. 174 | */ 175 | export function isAnyJson(value: unknown): value is AnyJson { 176 | return ( 177 | value === null || isString(value) || isNumber(value) || isBoolean(value) || isPlainObject(value) || isArray(value) 178 | ); 179 | } 180 | 181 | /** 182 | * Tests whether an `AnyJson` value is an object. 183 | * 184 | * @param value The value to test. 185 | */ 186 | export function isJsonMap(value: Optional): value is JsonMap { 187 | return isPlainObject(value); 188 | } 189 | 190 | /** 191 | * Tests whether an `AnyJson` value is an array. 192 | * 193 | * @param value The value to test. 194 | */ 195 | export function isJsonArray(value: Optional): value is JsonArray { 196 | return isArray(value); 197 | } 198 | 199 | /** 200 | * Tests whether or not a `key` string is a key of the given object type `T`. 201 | * 202 | * @param obj The target object to check the key in. 203 | * @param key The string to test as a key of the target object. 204 | */ 205 | export function isKeyOf>(obj: T, key: string): key is K { 206 | return Object.keys(obj).includes(key); 207 | } 208 | -------------------------------------------------------------------------------- /src/narrowing/object.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { KeyOf, Nullable } from '../types'; 9 | 10 | /** 11 | * Returns the keys of an object of type `T`. This is like `Object.keys` except the return type 12 | * captures the known keys of `T`. 13 | * 14 | * Note that it is the responsibility of the caller to use this wisely -- there are cases where 15 | * the runtime set of keys returned may be broader than the type checked set at compile time, 16 | * so there's potential for this to be abused in ways that are not inherently type safe. For 17 | * example, given base class `Animal`, subclass `Fish`, and `const animal: Animal = new Fish();` 18 | * then `keysOf(animal)` will not type-check the entire set of keys of the object `animal` since 19 | * it is actually an instance of type `Fish`, which has an extended property set. 20 | * 21 | * In general, it should be both convenient and type-safe to use this when enumerating the keys 22 | * of simple data objects with known properties. 23 | * 24 | * ``` 25 | * interface Point { x: number; y: number; } 26 | * const point: Point = { x: 1, y: 2 }; 27 | * const keys = keysOf(point); 28 | * // type of keys -> ('a' | 'b')[] 29 | * for (const key of keys) { 30 | * console.log(key, point[key]); 31 | * } 32 | * // x 1 33 | * // y 2 34 | * ``` 35 | * 36 | * @param obj The object of interest. 37 | */ 38 | export function keysOf>(obj: Nullable): K[] { 39 | return Object.keys(obj ?? {}) as K[]; 40 | } 41 | 42 | /** 43 | * Returns the entries of an object of type `T`. This is like `Object.entries` except the return type 44 | * captures the known keys and value types of `T`. 45 | * 46 | * Note that it is the responsibility of the caller to use this wisely -- there are cases where 47 | * the runtime set of entries returned may be broader than the type checked set at compile time, 48 | * so there's potential for this to be abused in ways that are not inherently type safe. For 49 | * example, given base class `Animal`, subclass `Fish`, and `const animal: Animal = new Fish();` 50 | * then `entriesOf(animal)` will not type-check the entire set of keys of the object `animal` since 51 | * it is actually an instance of type `Fish`, which has an extended property set. 52 | * 53 | * In general, it should be both convenient and type-safe to use this when enumerating the entries 54 | * of simple data objects with known properties. 55 | * 56 | * ``` 57 | * interface Point { x: number; y: number; } 58 | * const point: Point = { x: 1, y: 2 }; 59 | * // type of entries -> ['x' | 'y', number][] 60 | * const entries = entriesOf(point); 61 | * for (const entry of entries) { 62 | * console.log(entry[0], entry[1]); 63 | * } 64 | * // x 1 65 | * // y 2 66 | * ``` 67 | * 68 | * @param obj The object of interest. 69 | */ 70 | export function entriesOf>(obj: Nullable): Array<[K, T[K]]> { 71 | return Object.entries(obj ?? {}) as Array<[K, T[K]]>; 72 | } 73 | 74 | /** 75 | * Returns the values of an object of type `T`. This is like `Object.values` except the return type 76 | * captures the possible value types of `T`. 77 | * 78 | * Note that it is the responsibility of the caller to use this wisely -- there are cases where 79 | * the runtime set of values returned may be broader than the type checked set at compile time, 80 | * so there's potential for this to be abused in ways that are not inherently type safe. For 81 | * example, given base class `Animal`, subclass `Fish`, and `const animal: Animal = new Fish();` 82 | * then `valuesOf(animal)` will not type-check the entire set of values of the object `animal` since 83 | * it is actually an instance of type `Fish`, which has an extended property set. 84 | * 85 | * In general, it should be both convenient and type-safe to use this when enumerating the values 86 | * of simple data objects with known properties. 87 | * 88 | * ``` 89 | * interface Point { x: number; y: number; } 90 | * const point: Point = { x: 1, y: 2 }; 91 | * const values = valuesOf(point); 92 | * // type of values -> number[] 93 | * for (const value of values) { 94 | * console.log(value); 95 | * } 96 | * // 1 97 | * // 2 98 | * ``` 99 | * 100 | * @param obj The object of interest. 101 | */ 102 | export function valuesOf>(obj: Nullable): Array { 103 | return Object.values(obj ?? {}); 104 | } 105 | 106 | /** 107 | * Returns an array of all entry tuples of type `[K, NonNullable]` in an object `T` whose values are neither 108 | * `null` nor `undefined`. This can be convenient for enumerating the entries of unknown objects with optional 109 | * properties (including `Dictionary`s) without worrying about performing checks against possibly `undefined` or 110 | * `null` values. 111 | * 112 | * See also caveats outlined in {@link entriesOf}. 113 | * 114 | * @param obj The object of interest. 115 | */ 116 | export function definiteEntriesOf, V extends NonNullable>( 117 | obj: Nullable 118 | ): Array<[K, V]> { 119 | return entriesOf(obj).filter((entry): entry is [K, V] => entry[1] != null); 120 | } 121 | 122 | /** 123 | * Returns an array of all `string` keys in an object of type `T` whose values are neither `null` nor `undefined`. 124 | * This can be convenient for enumerating the keys of definitely assigned properties in an object or `Dictionary`. 125 | * 126 | * See also caveats outlined in {@link keysOf}. 127 | * 128 | * @param obj The object of interest. 129 | */ 130 | export function definiteKeysOf(obj: Nullable): Array> { 131 | return definiteEntriesOf(obj).map((entry) => entry[0]); 132 | } 133 | 134 | /** 135 | * Returns an array of all values of type `T` in an object `T` for values that are neither `null` nor `undefined`. 136 | * This can be convenient for enumerating the values of unknown objects with optional properties (including 137 | * `Dictionary`s) without worrying about performing checks against possibly `undefined` or `null` values. 138 | * 139 | * @param obj The object of interest. 140 | */ 141 | export function definiteValuesOf(obj: Nullable): Array]>> { 142 | return definiteEntriesOf(obj).map((entry) => entry[1]); 143 | } 144 | -------------------------------------------------------------------------------- /src/narrowing/to.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { JsonCloneError } from '../errors'; 9 | import { AnyJson, JsonArray, JsonMap, Nullable, Optional } from '../types'; 10 | import { asJsonArray, asJsonMap } from './as'; 11 | 12 | /** 13 | * Narrows an object of type `T` to an `AnyJson` following a deep, brute-force conversion of the object's data to 14 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 15 | * using the weaker `coerceAnyJson(unknown)` to type-narrow an arbitrary value to an `AnyJson` when the value's source 16 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 17 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 18 | * JSON data structure. Use `coerceAnyJson(unknown)` when the `value` object can be guaranteed to be JSON-compatible 19 | * and only needs type coercion. 20 | * 21 | * @param value The value to convert. 22 | * @throws {@link JsonCloneError} If the value values contain circular references. 23 | */ 24 | export function toAnyJson(value: Nullable): Optional; 25 | /** 26 | * Narrows an object of type `T` to an `AnyJson` following a deep, brute-force conversion of the object's data to 27 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 28 | * using the weaker `coerceAnyJson(unknown)` to type-narrow an arbitrary value to an `AnyJson` when the value's source 29 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 30 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 31 | * JSON data structure. Use `coerceAnyJson(unknown)` when the `value` object can be guaranteed to be JSON-compatible 32 | * and only needs type coercion. 33 | * 34 | * @param value The value to convert. 35 | * @param defaultValue The default to return if `value` was not defined. 36 | * @throws {@link JsonCloneError} If the value values contain circular references. 37 | */ 38 | export function toAnyJson(value: Nullable, defaultValue: AnyJson): AnyJson; 39 | // underlying function 40 | export function toAnyJson(value: Nullable, defaultValue?: AnyJson): Optional { 41 | try { 42 | return (value !== undefined ? JSON.parse(JSON.stringify(value)) : defaultValue) as Optional; 43 | } catch (err) { 44 | throw new JsonCloneError(err as Error); 45 | } 46 | } 47 | 48 | /** 49 | * Narrows an object of type `T` to a `JsonMap` following a deep, brute-force conversion of the object's data to 50 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 51 | * using the weaker `coerceJsonMap(object)` to type-narrow an arbitrary object to a `JsonMap` when the object's source 52 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 53 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 54 | * JSON data structure. Use `coerceJsonMap(object)` when the `value` object can be guaranteed to be JSON-compatible 55 | * and only needs type coercion. 56 | * 57 | * @param value The object to convert. 58 | * @throws {@link JsonCloneError} If the object values contain circular references. 59 | */ 60 | export function toJsonMap(value: T): JsonMap; 61 | /** 62 | * Narrows an object of type `T` to a `JsonMap` following a deep, brute-force conversion of the object's data to 63 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 64 | * using the weaker `coerceJsonMap(object)` to type-narrow an arbitrary object to a `JsonMap` when the object's source 65 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 66 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 67 | * JSON data structure. Use `coerceJsonMap(object)` when the `value` object can be guaranteed to be JSON-compatible 68 | * and only needs type coercion. 69 | * 70 | * @param value The object to convert. 71 | * @throws {@link JsonCloneError} If the object values contain circular references. 72 | */ 73 | export function toJsonMap(value: Nullable): Optional; 74 | /** 75 | * Narrows an object of type `T` to a `JsonMap` following a deep, brute-force conversion of the object's data to 76 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 77 | * using the weaker `coerceJsonMap(object)` to type-narrow an arbitrary object to a `JsonMap` when the object's source 78 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 79 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 80 | * JSON data structure. Use `coerceJsonMap(object)` when the `value` object can be guaranteed to be JSON-compatible 81 | * and only needs type coercion. 82 | * 83 | * @param value The object to convert. 84 | * @param defaultValue The default to return if `value` was not defined. 85 | * @throws {@link JsonCloneError} If the object values contain circular references. 86 | */ 87 | export function toJsonMap(value: Nullable, defaultValue: JsonMap): JsonMap; 88 | // underlying function 89 | export function toJsonMap(value: Nullable, defaultValue?: JsonMap): Optional { 90 | return asJsonMap(toAnyJson(value)) ?? defaultValue; 91 | } 92 | 93 | /** 94 | * Narrows an array of type `T` to a `JsonArray` following a deep, brute-force conversion of the array's data to 95 | * only consist of JSON-compatible values by performing a basic JSON clone on the array. This is preferable to 96 | * using the weaker `coerceJsonArray(array)` to type-narrow an arbitrary array to a `JsonArray` when the array's source 97 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 98 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 99 | * JSON data structure. Non-JSON entries will be converted to `null`s. Use `coerceJsonArray(array)` when the `value` 100 | * object can be guaranteed to be JSON-compatible and only needs type coercion. 101 | * 102 | * @param value The array to convert. 103 | * @throws {@link JsonCloneError} If the array values contain circular references. 104 | */ 105 | export function toJsonArray(value: T[]): JsonArray; 106 | /** 107 | * Narrows an array of type `T` to a `JsonArray` following a deep, brute-force conversion of the array's data to 108 | * only consist of JSON-compatible values by performing a basic JSON clone on the array. This is preferable to 109 | * using the weaker `coerceJsonArray(array)` to type-narrow an arbitrary array to a `JsonArray` when the array's source 110 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 111 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 112 | * JSON data structure. Non-JSON entries will be converted to `null`s. Use `coerceJsonArray(array)` when the `value` 113 | * object can be guaranteed to be JSON-compatible and only needs type coercion. 114 | * 115 | * @param value The array to convert. 116 | * @throws {@link JsonCloneError} If the array values contain circular references. 117 | */ 118 | export function toJsonArray(value: Optional): Optional; 119 | /** 120 | * Narrows an object of type `T` to a `JsonMap` following a deep, brute-force conversion of the object's data to 121 | * only consist of JSON-compatible values by performing a basic JSON clone on the object. This is preferable to 122 | * using the weaker `coerceJsonMap(object)` to type-narrow an arbitrary array to a `JsonMap` when the object's source 123 | * is unknown, but it comes with the increased overhead of performing the deep JSON clone to ensure runtime type 124 | * safety. The use of JSON cloning guarantees type safety by omitting non-JSON-compatible elements from the resulting 125 | * JSON data structure. Non-JSON entries will be converted to `null`s. Use `coerceJsonArray(array)` when the `value` 126 | * object can be guaranteed to be JSON-compatible and only needs type coercion. 127 | * 128 | * @param value The array to convert. 129 | * @param defaultValue The default to return if the value was undefined or of the incorrect type. 130 | * @throws {@link JsonCloneError} If the array values contain circular references. 131 | */ 132 | export function toJsonArray(value: Optional, defaultValue: JsonArray): JsonArray; 133 | // underlying method 134 | export function toJsonArray(value: Optional, defaultValue?: JsonArray): Optional { 135 | return asJsonArray(toAnyJson(value)) ?? defaultValue; 136 | } 137 | -------------------------------------------------------------------------------- /src/types/alias.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * An alias for the commonly needed `Extract`. 10 | */ 11 | export type KeyOf = Extract; 12 | 13 | /** 14 | * An alias for a tuple of type `[string, T]' for a given generic type `T`. `T` defaults to `unknown` if not otherwise 15 | * defined. 16 | */ 17 | export type KeyValue = [string, T]; 18 | -------------------------------------------------------------------------------- /src/types/collection.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * @module types 10 | */ 11 | 12 | import { Optional } from './union'; 13 | 14 | /** 15 | * An object with arbitrary string-indexed values of an optional generic type `Optional`. `T` defaults to `unknown` 16 | * when not explicitly supplied. For convenient iteration of definitely assigned (i.e. non-nullable) entries, keys, 17 | * and values, see the following functions: {@link definiteEntriesOf}, {@link definiteKeysOf}, and 18 | * {@link definiteValuesOf}. 19 | */ 20 | export type Dictionary = { 21 | [key: string]: Optional; 22 | }; 23 | 24 | /** 25 | * An alias for an array of `T` elements, where `T` defaults to `unknown`. 26 | */ 27 | export type AnyArray = T[]; 28 | 29 | /** 30 | * Any object with both a numeric index signature with values of type `T` and a numeric `length` 31 | * property. `T` defaults to `unknown` if unspecified. 32 | */ 33 | export type AnyArrayLike = ArrayLike; 34 | -------------------------------------------------------------------------------- /src/types/conditional.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * @module types 10 | */ 11 | 12 | /** 13 | * Subtracts `undefined` from any union type `T`. This is the opposite of {@link Optional}. 14 | */ 15 | export type NonOptional = T extends undefined ? never : T; 16 | 17 | /** 18 | * Converts a type `T` that may have optional properties into a type `T` with only required 19 | * properties (e.g. `undefined` values are not allowed). Explicit `null`s in value unions 20 | * will still be possible. This is similar to the `Required` builtin mapped type, but also 21 | * subtracts `undefined` from value union types as well as the optional property declaration. 22 | * 23 | * ``` 24 | * type Foo = { bar?: string | undefined | null }; 25 | * type RequiredNonOptionalFoo = RequiredNonOptional; 26 | * // RequiredNonOptionalFoo -> { bar: string | null }; 27 | * ``` 28 | */ 29 | export type RequiredNonOptional = T extends object ? { [P in keyof T]-?: NonOptional } : T; 30 | 31 | /** 32 | * Converts a type `T` that may have optional, nullable properties into a new type with only required 33 | * properties, while also subtracting `null` from all possible property values. 34 | * 35 | * ``` 36 | * type Foo = { bar?: string | undefined | null }; 37 | * type RequiredNonNullableFoo = RequiredNonNullable; 38 | * // RequiredNonNullableFoo -> { bar: string }; 39 | * ``` 40 | */ 41 | export type RequiredNonNullable = T extends object ? Required<{ [P in keyof T]: NonNullable }> : T; 42 | 43 | /** 44 | * Extracts literally defined property names from a type `T` as a union of key name strings, minus 45 | * any index signatures. 46 | */ 47 | export type Literals = Extract< 48 | { [K in keyof T]: string extends K ? never : number extends K ? never : K } extends { [_ in keyof T]: infer U } 49 | ? U 50 | : never, 51 | string 52 | >; 53 | -------------------------------------------------------------------------------- /src/types/function.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | // Needed for special types 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | /** 12 | * @module types 13 | */ 14 | 15 | /** 16 | * Any `function` returning type `T`. `T` defaults to `unknown` when not explicitly supplied. 17 | */ 18 | export type AnyFunction = (...args: any[]) => T; 19 | 20 | /** 21 | * A constructor for any type `T`. `T` defaults to `object` when not explicitly supplied. 22 | */ 23 | export type AnyConstructor = new (...args: any[]) => T; 24 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | export * from './alias'; 9 | export * from './collection'; 10 | export * from './conditional'; 11 | export * from './function'; 12 | export * from './json'; 13 | export * from './mapped'; 14 | export * from './union'; 15 | -------------------------------------------------------------------------------- /src/types/json.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * @module types 10 | */ 11 | 12 | import { Dictionary } from './collection'; 13 | 14 | /** 15 | * Any valid JSON primitive value. 16 | */ 17 | export type JsonPrimitive = null | boolean | number | string; 18 | 19 | /** 20 | * Any valid JSON collection value. 21 | */ 22 | export type JsonCollection = JsonMap | JsonArray; 23 | 24 | /** 25 | * Any valid JSON value. 26 | */ 27 | export type AnyJson = JsonPrimitive | JsonCollection; 28 | 29 | /** 30 | * Any JSON-compatible object. 31 | */ 32 | 33 | // leave this as an interface because it requires circular references that type aliases cannot do 34 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions 35 | export interface JsonMap extends Dictionary {} 36 | 37 | /** 38 | * Any JSON-compatible array. 39 | */ 40 | export type JsonArray = AnyJson[]; 41 | -------------------------------------------------------------------------------- /src/types/mapped.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * @module types 10 | */ 11 | 12 | import { Literals } from './conditional'; 13 | 14 | /** 15 | * Creates a new `Record` type from the literal properties of a type `T`, assigning their values 16 | * the to the type `U`. 17 | * 18 | * This can be useful for creating interfaces from the keys of an `enum` so that the keys are 19 | * available at runtime for meta-programming purposes, while both tying the properties of the 20 | * generated type to the enum keys and remaining as DRY as possible. 21 | * 22 | * ``` 23 | * enum QUERY_KEY { id, name, created, updated } 24 | * // type of QUERY_KEY -> { 25 | * // [x: number]: number; 26 | * // readonly id: number; 27 | * // readonly name: number; 28 | * // readonly created: number; 29 | * // readonly updated: number; 30 | * // } 31 | * interface QueryRecord extends LiteralsRecord { } 32 | * // type of QueryRecord -> { 33 | * // readonly id: string; 34 | * // readonly name: string; 35 | * // readonly created: string; 36 | * // readonly updated: string; 37 | * // } 38 | * // And for an interface with writable properties, use the following: 39 | * interface QueryRecord extends ReadWrite> { } 40 | * // type of QueryRecord -> { 41 | * // id: string; 42 | * // name: string; 43 | * // created: string; 44 | * // updated: string; 45 | * // } 46 | * ``` 47 | */ 48 | export type LiteralsRecord = Record, U>; 49 | 50 | /** 51 | * Creates a new type that omits keys in union type `K` of a target type `T`. 52 | */ 53 | export type Omit = Pick>; 54 | 55 | /** 56 | * Converts readonly properties of a type `T` to writable properties. This is the opposite of the 57 | * `Readonly` builtin mapped type. 58 | */ 59 | export type ReadWrite = { -readonly [K in keyof T]: T[K] }; 60 | 61 | /** 62 | * A view over an `object` with constrainable properties. 63 | */ 64 | export type View = { [_ in K]: V }; 65 | 66 | /** 67 | * Returns a new type consisting of all properties declared for an input type `T2` overlaid on the 68 | * properties of type `T1`. Any definitions in `T2` replace those previously defined in `T1`. This can 69 | * be useful for redefining the types of properties on `T1` with values from an inline type `T2`, perhaps to 70 | * change their type or to make them optional. 71 | * 72 | * ``` 73 | * type NameAndStringValue = { name: string, value: string } 74 | * type NameAndOptionalNumericValue = Overwrite 75 | * // type of NameAndOptionalNumericValue -> { name: string } & { value?: number | undefined } 76 | * ``` 77 | */ 78 | export type Overwrite = { [P in Exclude]: T1[P] } & T2; 79 | -------------------------------------------------------------------------------- /src/types/union.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /** 9 | * @module types 10 | */ 11 | 12 | /** 13 | * A union type for either the parameterized type `T` or `undefined` -- the opposite of {@link NonOptional}. 14 | */ 15 | export type Optional = T | undefined; 16 | 17 | /** 18 | * A union type for either the parameterized type `T`, `null`, or `undefined` -- the opposite of 19 | * the `NonNullable` builtin conditional type. 20 | */ 21 | export type Nullable = Optional; 22 | 23 | /** 24 | * A union type for either the parameterized type `T` or an array of `T`. 25 | */ 26 | export type Many = T | T[]; 27 | -------------------------------------------------------------------------------- /test/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '../.eslintrc.cjs', 3 | // Allow describe and it 4 | env: { mocha: true }, 5 | rules: { 6 | // Allow assert style expressions. i.e. expect(true).to.be.true 7 | 'no-unused-expressions': 'off', 8 | 9 | // The test need to do a lot of these to test different combinations of fake data 10 | '@typescript-eslint/explicit-function-return-type': 'off', 11 | '@typescript-eslint/no-empty-function': 'off', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/experimental/index.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | // tslint:disable:no-unused-expression 9 | 10 | import { expect } from 'chai'; 11 | import { as, is, ObjectShape } from '../../src/experimental'; 12 | 13 | class TestClass { 14 | public foo = 'bar'; 15 | } 16 | 17 | type Test = { 18 | s: string; 19 | b?: boolean; 20 | c?: TestClass; 21 | } 22 | 23 | describe('experimental', () => { 24 | const testShape: ObjectShape = { 25 | s: 'string', 26 | b: { 27 | type: 'boolean', 28 | optional: true, 29 | }, 30 | c: { 31 | type: TestClass, 32 | optional: true, 33 | }, 34 | }; 35 | 36 | describe('is', () => { 37 | it('should return false if an object conforms to a given shape', () => { 38 | const o: object = { s: false }; 39 | expect(is(o, testShape)).to.be.false; 40 | }); 41 | 42 | it('should return true if an object conforms to a given shape', () => { 43 | const o: object = { s: 'string', b: false, c: new TestClass() }; 44 | expect(is(o, testShape)).to.be.true; 45 | }); 46 | }); 47 | 48 | describe('as', () => { 49 | it('should return a typed object if it does not conform to a given shape', () => { 50 | const o: object = { s: false }; 51 | expect(as(o, testShape)).to.be.undefined; 52 | }); 53 | 54 | it('should return a typed object if it conforms to a given shape', () => { 55 | const o: object = { s: 'string', b: false, c: new TestClass() }; 56 | expect(as(o, testShape)).to.equal(o); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/narrowing/as.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { asBoolean, asJsonArray, asJsonMap, asNumber, asString } from '../../src/narrowing/as'; 10 | 11 | describe('as type', () => { 12 | describe('asString', () => { 13 | it('should return undefined when passed undefined', () => { 14 | expect(asString(undefined)).to.be.undefined; 15 | }); 16 | 17 | it('should return a string when passed a string', () => { 18 | const value = 'string'; 19 | expect(asString(value)).to.equal(value); 20 | }); 21 | 22 | it('should return the default when passed undefined and a default', () => { 23 | const def = 'string'; 24 | expect(asString(undefined, def)).to.equal(def); 25 | }); 26 | }); 27 | 28 | describe('asNumber', () => { 29 | it('should return undefined when passed undefined', () => { 30 | expect(asNumber(undefined)).to.be.undefined; 31 | }); 32 | 33 | it('should return a number when passed a number', () => { 34 | const value = 1; 35 | expect(asNumber(value)).to.equal(value); 36 | }); 37 | 38 | it('should return the default when passed undefined and a default', () => { 39 | const def = 1; 40 | expect(asNumber(undefined, def)).to.equal(def); 41 | }); 42 | }); 43 | 44 | describe('asBoolean', () => { 45 | it('should return undefined when passed undefined', () => { 46 | expect(asBoolean(undefined)).to.be.undefined; 47 | }); 48 | 49 | it('should return a boolean when passed a boolean', () => { 50 | const value = true; 51 | expect(asBoolean(value)).to.equal(value); 52 | }); 53 | 54 | it('should return the default when passed undefined and a default', () => { 55 | const def = true; 56 | expect(asBoolean(undefined, def)).to.equal(def); 57 | }); 58 | }); 59 | 60 | describe('asJsonMap', () => { 61 | it('should return undefined when passed undefined', () => { 62 | expect(asJsonMap(undefined)).to.be.undefined; 63 | }); 64 | 65 | it('should return a JsonMap when passed a JsonMap', () => { 66 | const value = { a: 'b', c: 'd' }; 67 | expect(asJsonMap(value)).to.equal(value); 68 | }); 69 | 70 | it('should return the default when passed undefined and a default', () => { 71 | const def = { a: 'b', c: 'd' }; 72 | expect(asJsonMap(undefined, def)).to.equal(def); 73 | }); 74 | }); 75 | 76 | describe('asJsonArray', () => { 77 | it('should return undefined when passed undefined', () => { 78 | expect(asJsonArray(undefined)).to.be.undefined; 79 | }); 80 | 81 | it('should return a JsonArray when passed a JsonArray', () => { 82 | const value = ['a', 'b']; 83 | expect(asJsonArray(value)).to.equal(value); 84 | }); 85 | 86 | it('should return the default when passed undefined and a default', () => { 87 | const def = ['a', 'b']; 88 | expect(asJsonArray(undefined, def)).to.equal(def); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/narrowing/assert.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { AssertionFailedError } from '../../src/errors'; 10 | import { 11 | assert, 12 | assertAnyJson, 13 | assertArray, 14 | assertBoolean, 15 | assertDictionary, 16 | assertFunction, 17 | assertInstance, 18 | assertJsonArray, 19 | assertJsonMap, 20 | assertNonNull, 21 | assertNumber, 22 | assertObject, 23 | assertPlainObject, 24 | assertString, 25 | } from '../../src/narrowing/assert'; 26 | 27 | class TestClass { 28 | public constructor(public name = 'test') {} 29 | } 30 | 31 | describe('assert type', () => { 32 | describe('assert', () => { 33 | it('should do nothing when passed true', () => { 34 | assert(true); 35 | }); 36 | 37 | it('should raise an error when passed false', () => { 38 | expect(() => assert(false)).to.throw(AssertionFailedError); 39 | }); 40 | }); 41 | 42 | describe('assertNonNull', () => { 43 | it('should raise an error when passed undefined', () => { 44 | expect(() => assertNonNull(undefined)).to.throw(AssertionFailedError); 45 | }); 46 | 47 | it('should raise an error when passed null', () => { 48 | expect(() => assertNonNull(null)).to.throw(AssertionFailedError); 49 | }); 50 | 51 | it('should do nothing given a non-nullish value', () => { 52 | const value = 'string'; 53 | assertNonNull(value); 54 | }); 55 | }); 56 | 57 | describe('assertString', () => { 58 | it('should raise an error when passed undefined', () => { 59 | expect(() => assertString(undefined)).to.throw(AssertionFailedError); 60 | }); 61 | 62 | it('should do nothing when passed a string', () => { 63 | const value = 'string'; 64 | assertString(value); 65 | }); 66 | }); 67 | 68 | describe('assertNumber', () => { 69 | it('should raise an error when passed undefined', () => { 70 | expect(() => assertNumber(undefined)).to.throw(AssertionFailedError); 71 | }); 72 | 73 | it('should do nothing when passed a number', () => { 74 | const value = 0; 75 | assertNumber(value); 76 | }); 77 | }); 78 | 79 | describe('assertBoolean', () => { 80 | it('should raise an error when passed undefined', () => { 81 | expect(() => assertBoolean(undefined)).to.throw(AssertionFailedError); 82 | }); 83 | 84 | it('should do nothing when passed a boolean', () => { 85 | const value = true; 86 | assertBoolean(value); 87 | }); 88 | }); 89 | 90 | describe('assertObject', () => { 91 | it('should raise an error when passed undefined', () => { 92 | expect(() => assertObject(undefined)).to.throw(AssertionFailedError); 93 | }); 94 | 95 | it('should do nothing when passed a object', () => { 96 | const value = { a: 'b' }; 97 | assertObject(value); 98 | }); 99 | }); 100 | 101 | describe('assertPlainObject', () => { 102 | it('should raise an error when passed undefined', () => { 103 | expect(() => assertPlainObject(undefined)).to.throw(AssertionFailedError); 104 | }); 105 | 106 | it('should do nothing when passed a plain object', () => { 107 | const value = { a: 'b' }; 108 | assertPlainObject(value); 109 | }); 110 | }); 111 | 112 | describe('assertDictionary', () => { 113 | it('should raise an error when passed undefined', () => { 114 | expect(() => assertDictionary(undefined)).to.throw(AssertionFailedError); 115 | }); 116 | 117 | it('should do nothing when passed a dictionary object', () => { 118 | const value = { a: 'b' }; 119 | assertDictionary(value); 120 | }); 121 | }); 122 | 123 | describe('assertInstance', () => { 124 | it('should raise an error when passed undefined', () => { 125 | expect(() => assertInstance(undefined, TestClass)).to.throw(AssertionFailedError); 126 | }); 127 | 128 | it('should do nothing when passed a class instance', () => { 129 | const value = new TestClass('foo'); 130 | assertInstance(value, TestClass); 131 | }); 132 | }); 133 | 134 | describe('assertArray', () => { 135 | it('should raise an error when passed undefined', () => { 136 | expect(() => assertArray(undefined)).to.throw(AssertionFailedError); 137 | }); 138 | 139 | it('should do nothing when passed an array', () => { 140 | const value = ['a', 'b']; 141 | assertArray(value); 142 | }); 143 | }); 144 | 145 | describe('assertFunction', () => { 146 | it('should raise an error when passed undefined', () => { 147 | expect(() => assertFunction(undefined)).to.throw(AssertionFailedError); 148 | }); 149 | 150 | it('should do nothing when passed a function', () => { 151 | const value = () => {}; 152 | assertFunction(value); 153 | }); 154 | }); 155 | 156 | describe('assertAnyJson', () => { 157 | it('should raise an error when passed undefined', () => { 158 | expect(() => assertAnyJson(undefined)).to.throw(AssertionFailedError); 159 | }); 160 | 161 | it('should do nothing when passed a string', () => { 162 | const value = 'string'; 163 | assertAnyJson(value); 164 | }); 165 | }); 166 | 167 | describe('assertJsonMap', () => { 168 | it('should raise an error when passed undefined', () => { 169 | expect(() => assertJsonMap(undefined)).to.throw(AssertionFailedError); 170 | }); 171 | 172 | it('should do nothing when passed a JsonMap', () => { 173 | const value = { a: 'b', c: 'd' }; 174 | assertJsonMap(value); 175 | }); 176 | }); 177 | 178 | describe('assertJsonArray', () => { 179 | it('should raise an error when passed undefined', () => { 180 | expect(() => assertJsonArray(undefined)).to.throw(AssertionFailedError); 181 | }); 182 | 183 | it('should do nothing when passed a JsonArray', () => { 184 | const value = ['a', 'b']; 185 | assertJsonArray(value); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /test/narrowing/coerce.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { coerceAnyJson, coerceJsonArray, coerceJsonMap } from '../../src/narrowing/coerce'; 10 | 11 | describe('coerce type', () => { 12 | describe('coerceAnyJson', () => { 13 | it('should return undefined when passed undefined', () => { 14 | expect(coerceAnyJson(undefined)).to.be.undefined; 15 | }); 16 | 17 | it('should return a string when passed a string', () => { 18 | const value = 'string'; 19 | expect(coerceAnyJson(value)).to.equal(value); 20 | }); 21 | 22 | it('should return the default when passed undefined and a default', () => { 23 | const def = 'string'; 24 | expect(coerceAnyJson(undefined, def)).to.equal(def); 25 | }); 26 | }); 27 | 28 | describe('coerceJsonMap', () => { 29 | it('should return undefined when passed undefined', () => { 30 | expect(coerceJsonMap(undefined)).to.be.undefined; 31 | }); 32 | 33 | it('should return a JsonMap when passed a JsonMap', () => { 34 | const value = { a: 'b', c: 'd' }; 35 | expect(coerceJsonMap(value)).to.equal(value); 36 | }); 37 | 38 | it('should return the default when passed undefined and a default', () => { 39 | const def = { a: 'b', c: 'd' }; 40 | expect(coerceJsonMap(undefined, def)).to.equal(def); 41 | }); 42 | }); 43 | 44 | describe('coerceJsonArray', () => { 45 | it('should return undefined when passed undefined', () => { 46 | expect(coerceJsonArray(undefined)).to.be.undefined; 47 | }); 48 | 49 | it('should return a JsonArray when passed a JsonArray', () => { 50 | const value = ['a', 'b']; 51 | expect(coerceJsonArray(value)).to.equal(value); 52 | }); 53 | 54 | it('should return the default when passed undefined and a default', () => { 55 | const def = ['a', 'b']; 56 | expect(coerceJsonArray(undefined, def)).to.equal(def); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/narrowing/ensure.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { UnexpectedValueTypeError } from '../../src/errors'; 10 | import { 11 | ensure, 12 | ensureAnyJson, 13 | ensureArray, 14 | ensureBoolean, 15 | ensureDictionary, 16 | ensureFunction, 17 | ensureInstance, 18 | ensureJsonArray, 19 | ensureJsonMap, 20 | ensureNumber, 21 | ensureObject, 22 | ensurePlainObject, 23 | ensureString, 24 | } from '../../src/narrowing/ensure'; 25 | 26 | class TestClass { 27 | public constructor(public name = 'test') {} 28 | } 29 | 30 | describe('ensure type', () => { 31 | describe('ensure', () => { 32 | it('should raise an error when passed undefined', () => { 33 | expect(() => ensure(undefined)).to.throw(UnexpectedValueTypeError); 34 | }); 35 | 36 | it('should raise an error when passed null', () => { 37 | expect(() => ensure(null)).to.throw(UnexpectedValueTypeError); 38 | }); 39 | 40 | it('should return a string when passed a string', () => { 41 | const value = 'string'; 42 | expect(ensure(value)).to.equal(value); 43 | }); 44 | }); 45 | 46 | describe('ensureString', () => { 47 | it('should raise an error when passed undefined', () => { 48 | expect(() => ensureString(undefined)).to.throw(UnexpectedValueTypeError); 49 | }); 50 | 51 | it('should return a string when passed a string', () => { 52 | const value = 'string'; 53 | expect(ensureString(value)).to.equal(value); 54 | }); 55 | }); 56 | 57 | describe('ensureNumber', () => { 58 | it('should raise an error when passed undefined', () => { 59 | expect(() => ensureNumber(undefined)).to.throw(UnexpectedValueTypeError); 60 | }); 61 | 62 | it('should return a number when passed a number', () => { 63 | const value = 1; 64 | expect(ensureNumber(value)).to.equal(value); 65 | }); 66 | }); 67 | 68 | describe('ensureBoolean', () => { 69 | it('should raise an error when passed undefined', () => { 70 | expect(() => ensureBoolean(undefined)).to.throw(UnexpectedValueTypeError); 71 | }); 72 | 73 | it('should return a boolean when passed a boolean', () => { 74 | const value = true; 75 | expect(ensureBoolean(value)).to.equal(value); 76 | }); 77 | }); 78 | 79 | describe('ensureObject', () => { 80 | it('should raise an error when passed undefined', () => { 81 | expect(() => ensureObject(undefined)).to.throw(UnexpectedValueTypeError); 82 | }); 83 | 84 | it('should return a object when passed a object', () => { 85 | const value = { a: 'b' }; 86 | expect(ensureObject(value)).to.equal(value); 87 | }); 88 | }); 89 | 90 | describe('ensurePlainObject', () => { 91 | it('should raise an error when passed undefined', () => { 92 | expect(() => ensurePlainObject(undefined)).to.throw(UnexpectedValueTypeError); 93 | }); 94 | 95 | it('should return a plain object when passed a plain object', () => { 96 | const value = { a: 'b' }; 97 | expect(ensurePlainObject(value)).to.equal(value); 98 | }); 99 | }); 100 | 101 | describe('ensureDictionary', () => { 102 | it('should raise an error when passed undefined', () => { 103 | expect(() => ensureDictionary(undefined)).to.throw(UnexpectedValueTypeError); 104 | }); 105 | 106 | it('should return a dictionary object when passed a dictionary object', () => { 107 | const value = { a: 'b' }; 108 | expect(ensureDictionary(value)).to.equal(value); 109 | }); 110 | }); 111 | 112 | describe('ensureInstance', () => { 113 | it('should raise an error when passed undefined', () => { 114 | expect(() => ensureInstance(undefined, TestClass)).to.throw(UnexpectedValueTypeError); 115 | }); 116 | 117 | it('should return a class instance when passed a class instance', () => { 118 | const value = new TestClass('foo'); 119 | expect(ensureInstance(value, TestClass)).to.equal(value); 120 | }); 121 | }); 122 | 123 | describe('ensureArray', () => { 124 | it('should raise an error when passed undefined', () => { 125 | expect(() => ensureArray(undefined)).to.throw(UnexpectedValueTypeError); 126 | }); 127 | 128 | it('should return an array when passed an array', () => { 129 | const value = ['a', 'b']; 130 | expect(ensureArray(value)).to.equal(value); 131 | }); 132 | }); 133 | 134 | describe('ensureFunction', () => { 135 | it('should raise an error when passed undefined', () => { 136 | expect(() => ensureFunction(undefined)).to.throw(UnexpectedValueTypeError); 137 | }); 138 | 139 | it('should return a function when passed a function', () => { 140 | const value = () => {}; 141 | expect(ensureFunction(value)).to.equal(value); 142 | }); 143 | }); 144 | 145 | describe('ensureAnyJson', () => { 146 | it('should raise an error when passed undefined', () => { 147 | expect(() => ensureAnyJson(undefined)).to.throw(UnexpectedValueTypeError); 148 | }); 149 | 150 | it('should return a string when passed a string', () => { 151 | const value = 'string'; 152 | expect(ensureAnyJson(value)).to.equal(value); 153 | }); 154 | }); 155 | 156 | describe('ensureJsonMap', () => { 157 | it('should raise an error when passed undefined', () => { 158 | expect(() => ensureJsonMap(undefined)).to.throw(UnexpectedValueTypeError); 159 | }); 160 | 161 | it('should return a JsonMap when passed a JsonMap', () => { 162 | const value = { a: 'b', c: 'd' }; 163 | expect(ensureJsonMap(value)).to.deep.equal(value); 164 | }); 165 | }); 166 | 167 | describe('ensureJsonArray', () => { 168 | it('should raise an error when passed undefined', () => { 169 | expect(() => ensureJsonArray(undefined)).to.throw(UnexpectedValueTypeError); 170 | }); 171 | 172 | it('should return a JsonArray when passed a JsonArray', () => { 173 | const value = ['a', 'b']; 174 | expect(ensureJsonArray(value)).to.deep.equal(value); 175 | }); 176 | }); 177 | }); 178 | -------------------------------------------------------------------------------- /test/narrowing/get.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { 10 | get, 11 | getAnyJson, 12 | getArray, 13 | getBoolean, 14 | getFunction, 15 | getInstance, 16 | getJsonArray, 17 | getJsonMap, 18 | getNumber, 19 | getObject, 20 | getPlainObject, 21 | getString, 22 | getDictionary, 23 | } from '../../src/narrowing/get'; 24 | 25 | class TestClass { 26 | public constructor(public name = 'test') {} 27 | } 28 | 29 | describe('get type', () => { 30 | const jsonMap = { 31 | inner: { 32 | s: 'string', 33 | n: 1, 34 | b: true, 35 | m: { a: 'b', c: 'd', e: [1] }, 36 | a: ['a', 'b'], 37 | ['m.a']: { c: 'd', e: [1] }, 38 | }, 39 | }; 40 | 41 | const jsonArray = [jsonMap]; 42 | 43 | const obj = { 44 | inner: Object.assign(jsonMap.inner, { 45 | c: new TestClass(), 46 | f: () => {}, 47 | }), 48 | }; 49 | 50 | describe('get', () => { 51 | it('should return undefined when passed an undefined object', () => { 52 | expect(get(undefined, 'foo')).to.be.undefined; 53 | }); 54 | 55 | it('should return undefined when passed a null object', () => { 56 | expect(get(null, 'foo')).to.be.undefined; 57 | }); 58 | 59 | it('should return undefined when passed an unknown path', () => { 60 | expect(get(jsonMap, 'foo')).to.be.undefined; 61 | }); 62 | 63 | it('should return a default when passed an unknown path and a default', () => { 64 | const def = 'def'; 65 | expect(get(jsonMap, 'foo', def)).to.equal(def); 66 | }); 67 | 68 | it('should return a value from an object when passed a valid object path', () => { 69 | const value = jsonMap.inner.s; 70 | const path = 'inner.s'; 71 | expect(get(jsonMap, path)).to.equal(value); 72 | }); 73 | 74 | it('should return a value from an object when passed a valid object path containing a period', () => { 75 | const value = jsonMap.inner['m.a']; 76 | const path = 'inner["m.a"]'; 77 | expect(get(jsonMap, path)).to.equal(value); 78 | }); 79 | 80 | it('should return a value from an object when passed a valid object path containing a period and array', () => { 81 | const value = jsonMap.inner['m.a'].e[0]; 82 | const path = 'inner["m.a"].e[0]'; 83 | expect(get(jsonMap, path)).to.equal(value); 84 | }); 85 | 86 | it('should return a value from an object when passed a valid object path containing a period and key in brackets', () => { 87 | const value = jsonMap.inner['m.a'].e[0]; 88 | const path = 'inner["m.a"]["e"][0]'; 89 | expect(get(jsonMap, path)).to.equal(value); 90 | }); 91 | 92 | it('should return a value from an array when passed a valid array path', () => { 93 | const value = jsonArray[0].inner.a[1]; 94 | const path = '[0].inner.a[1]'; 95 | expect(get(jsonArray, path)).to.equal(value); 96 | }); 97 | 98 | it('should support string keys in brackets, with or without any style of quotes', () => { 99 | const value = jsonArray[0].inner.m.e[0]; 100 | const path = '[0]["inner"][m][\'e\'][0]'; 101 | expect(get(jsonArray, path)).to.equal(value); 102 | }); 103 | }); 104 | 105 | describe('getString', () => { 106 | it('should return a default when passed an unknown path and a default', () => { 107 | const def = 'def'; 108 | expect(getString(jsonMap, 'foo', def)).to.equal(def); 109 | }); 110 | 111 | it('should return a string when passed a path to a string', () => { 112 | const value = jsonMap.inner.s; 113 | const path = 'inner.s'; 114 | expect(getString(jsonMap, path)).to.equal(value); 115 | }); 116 | 117 | it('should return undefined when passed a path to a non-string', () => { 118 | const path = 'inner.b'; 119 | expect(getString(jsonMap, path)).to.be.undefined; 120 | }); 121 | }); 122 | 123 | describe('getNumber', () => { 124 | it('should return a default when passed an unknown path and a default', () => { 125 | const def = 1; 126 | expect(getNumber(jsonMap, 'foo', def)).to.equal(def); 127 | }); 128 | 129 | it('should return a number when passed a path to a number', () => { 130 | const value = jsonMap.inner.n; 131 | const path = 'inner.n'; 132 | expect(getNumber(jsonMap, path)).to.equal(value); 133 | }); 134 | 135 | it('should return undefined when passed a path to a non-number', () => { 136 | const path = 'inner.s'; 137 | expect(getNumber(jsonMap, path)).to.be.undefined; 138 | }); 139 | }); 140 | 141 | describe('getBoolean', () => { 142 | it('should return a default when passed an unknown path and a default', () => { 143 | const def = true; 144 | expect(getBoolean(jsonMap, 'foo', def)).to.equal(def); 145 | }); 146 | 147 | it('should return a boolean when passed a path to a boolean', () => { 148 | const value = jsonMap.inner.b; 149 | const path = 'inner.b'; 150 | expect(getBoolean(jsonMap, path)).to.equal(value); 151 | }); 152 | 153 | it('should return undefined when passed a path to a non-boolean', () => { 154 | const path = 'inner.s'; 155 | expect(getBoolean(jsonMap, path)).to.be.undefined; 156 | }); 157 | }); 158 | 159 | describe('getObject', () => { 160 | it('should return a default when passed an unknown path and a default', () => { 161 | const def = { a: 'b' }; 162 | expect(getObject(jsonMap, 'foo', def)).to.equal(def); 163 | }); 164 | 165 | it('should return an object when passed a path to an object', () => { 166 | const value = jsonMap.inner.m; 167 | const path = 'inner.m'; 168 | expect(getObject(jsonMap, path)).to.equal(value); 169 | }); 170 | 171 | it('should return undefined when passed a path to a non-object', () => { 172 | const path = 'inner.s'; 173 | expect(getObject(jsonMap, path)).to.be.undefined; 174 | }); 175 | }); 176 | 177 | describe('getPlainObject', () => { 178 | it('should return a default when passed an unknown path and a default', () => { 179 | const def = { a: 'b' }; 180 | expect(getPlainObject(jsonMap, 'foo', def)).to.equal(def); 181 | }); 182 | 183 | it('should return a plain object when passed a path to an object', () => { 184 | const value = jsonMap.inner.m; 185 | const path = 'inner.m'; 186 | expect(getPlainObject(jsonMap, path)).to.equal(value); 187 | }); 188 | 189 | it('should return undefined when passed a path to a non-object', () => { 190 | const path = 'inner.s'; 191 | expect(getPlainObject(jsonMap, path)).to.be.undefined; 192 | }); 193 | }); 194 | 195 | describe('getDictionary', () => { 196 | it('should return a default when passed an unknown path and a default', () => { 197 | const def = { a: 'b' }; 198 | expect(getDictionary(jsonMap, 'foo', def)).to.equal(def); 199 | }); 200 | 201 | it('should return a dictionary when passed a path to an object', () => { 202 | const value = jsonMap.inner.m; 203 | const path = 'inner.m'; 204 | expect(getDictionary(jsonMap, path)).to.equal(value); 205 | }); 206 | 207 | it('should return undefined when passed a path to a non-object', () => { 208 | const path = 'inner.s'; 209 | expect(getDictionary(jsonMap, path)).to.be.undefined; 210 | }); 211 | }); 212 | 213 | describe('getInstance', () => { 214 | it('should return a default when passed an unknown path and a default', () => { 215 | const def = new TestClass('mine'); 216 | expect(getInstance(obj, 'foo', TestClass, def)).to.equal(def); 217 | }); 218 | 219 | it('should return a class instance when passed a path to a class instance', () => { 220 | const value = obj.inner.c; 221 | const path = 'inner.c'; 222 | expect(getInstance(obj, path, TestClass)).to.equal(value); 223 | }); 224 | 225 | it('should return undefined when passed a path to a non-instance', () => { 226 | const path = 'inner.s'; 227 | expect(getInstance(jsonMap, path, TestClass)).to.be.undefined; 228 | }); 229 | }); 230 | 231 | describe('getArray', () => { 232 | it('should return a default when passed an unknown path and a default', () => { 233 | const def = ['a', 'b']; 234 | expect(getArray(jsonMap, 'foo', def)).to.equal(def); 235 | }); 236 | 237 | it('should return an array when passed a path to an array', () => { 238 | const value = jsonMap.inner.a; 239 | const path = 'inner.a'; 240 | expect(getArray(jsonMap, path)).to.equal(value); 241 | }); 242 | 243 | it('should return undefined when passed a path to a non-array', () => { 244 | const path = 'inner.s'; 245 | expect(getArray(jsonMap, path)).to.be.undefined; 246 | }); 247 | }); 248 | 249 | describe('getFunction', () => { 250 | it('should return a default when passed an unknown path and a default', () => { 251 | const def = () => {}; 252 | expect(getFunction(obj, 'foo', def)).to.equal(def); 253 | }); 254 | 255 | it('should return a class instance when passed a path to a class instance', () => { 256 | const value = obj.inner.f; 257 | const path = 'inner.f'; 258 | expect(getFunction(obj, path)).to.equal(value); 259 | }); 260 | 261 | it('should return undefined when passed a path to a non-function', () => { 262 | const path = 'inner.s'; 263 | expect(getFunction(jsonMap, path)).to.be.undefined; 264 | }); 265 | }); 266 | 267 | describe('getAnyJson', () => { 268 | it('should return undefined when passed an unknown path', () => { 269 | expect(getAnyJson(jsonMap, 'foo')).to.be.undefined; 270 | }); 271 | 272 | it('should return a default when passed an unknown path and a default', () => { 273 | const def = 'def'; 274 | expect(getAnyJson(jsonMap, 'foo', def)).to.equal(def); 275 | }); 276 | 277 | it('should return a string when passed a path to a string', () => { 278 | const value = jsonMap.inner.s; 279 | const path = 'inner.s'; 280 | expect(getAnyJson(jsonMap, path)).to.equal(value); 281 | }); 282 | 283 | it('should return an array element when passed a path containing an array index', () => { 284 | const value = jsonMap.inner.a[1]; 285 | const path = '[0].inner.a[1]'; 286 | expect(getAnyJson(jsonArray, path)).to.equal(value); 287 | }); 288 | 289 | it('should return a string when passed a path to a string array index', () => { 290 | const value = jsonMap.inner.a[1]; 291 | const path = 'inner.a[1]'; 292 | expect(getAnyJson(jsonMap, path)).to.equal(value); 293 | }); 294 | 295 | it('should return an array element when passed a path containing an array index', () => { 296 | const value = jsonArray[0].inner.a[1]; 297 | const path = '[0].inner.a[1]'; 298 | expect(getAnyJson(jsonArray, path)).to.equal(value); 299 | }); 300 | 301 | it('should return undefined when passed a path to a non-json value', () => { 302 | const path = 'inner.f'; 303 | expect(getAnyJson(jsonMap, path)).to.be.undefined; 304 | }); 305 | }); 306 | 307 | describe('getJsonMap', () => { 308 | it('should return a default when passed an unknown path and a default', () => { 309 | const def = { a: 'b', c: 'd' }; 310 | expect(getJsonMap(jsonMap, 'foo', def)).to.equal(def); 311 | }); 312 | 313 | it('should return a JsonMap when passed a path to a JsonMap', () => { 314 | const value = jsonMap.inner.m; 315 | const path = 'inner.m'; 316 | expect(getJsonMap(jsonMap, path)).to.deep.equal(value); 317 | }); 318 | 319 | it('should return undefined when passed a path to a non-JsonMap', () => { 320 | const path = 'inner.f'; 321 | expect(getJsonMap(jsonMap, path)).to.be.undefined; 322 | }); 323 | }); 324 | 325 | describe('getJsonArray', () => { 326 | it('should return a default when passed an unknown path and a default', () => { 327 | const def = ['a', 'b']; 328 | expect(getJsonArray(jsonMap, 'foo', def)).to.equal(def); 329 | }); 330 | 331 | it('should return a JsonArray when passed a path to a JsonArray', () => { 332 | const value = jsonMap.inner.a; 333 | const path = 'inner.a'; 334 | expect(getJsonArray(jsonMap, path)).to.deep.equal(value); 335 | }); 336 | 337 | it('should return undefined when passed a path to a non-JsonArray value', () => { 338 | const path = 'inner.f'; 339 | expect(getJsonArray(jsonMap, path)).to.be.undefined; 340 | }); 341 | }); 342 | }); 343 | -------------------------------------------------------------------------------- /test/narrowing/has.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { isAnyJson } from '../../src/narrowing'; 10 | import { 11 | has, 12 | hasAnyJson, 13 | hasArray, 14 | hasBoolean, 15 | hasFunction, 16 | hasInstance, 17 | hasJsonArray, 18 | hasJsonMap, 19 | hasNumber, 20 | hasObject, 21 | hasPlainObject, 22 | hasString, 23 | hasDictionary, 24 | } from '../../src/narrowing/has'; 25 | 26 | class TestClass { 27 | public constructor(public name = 'test') {} 28 | } 29 | 30 | describe('has type', () => { 31 | let obj: unknown; 32 | 33 | beforeEach(() => { 34 | obj = { 35 | u: undefined, 36 | l: null, 37 | s: '', 38 | b: false, 39 | n: 0, 40 | m: {}, 41 | i: new TestClass(), 42 | a: [], 43 | f: () => {}, 44 | }; 45 | }); 46 | 47 | describe('has', () => { 48 | it('should fail to narrow an unknown type if the target key does not exist', () => { 49 | if (has(obj, 'z')) { 50 | throw new Error('object should not have property z'); 51 | } 52 | }); 53 | 54 | it('should narrow an unknown type when the target key is found, even if undefined', () => { 55 | if (!has(obj, 'u')) { 56 | throw new Error('object should have property u'); 57 | } 58 | // trivial runtime check but useful for compilation testing 59 | expect(obj.u).to.equal(obj.u); 60 | }); 61 | 62 | it('should narrow an unknown type when the target key is found', () => { 63 | if (!has(obj, 's')) { 64 | throw new Error('object should have property s'); 65 | } 66 | // trivial runtime check but useful for compilation testing 67 | expect(obj.s).to.equal(obj.s); 68 | }); 69 | 70 | it('should progressively narrow an unknown type when target keys are found', () => { 71 | if (!has(obj, 's')) { 72 | throw new Error('object should have property s'); 73 | } 74 | // trivial runtime check but useful for compilation testing 75 | expect(obj.s).to.equal(obj.s); 76 | if (!has(obj, 'b')) { 77 | throw new Error('object should have property b'); 78 | } 79 | // trivial runtime checks but useful for compilation testing 80 | expect(obj.s).to.equal(obj.s); 81 | expect(obj.b).to.equal(obj.b); 82 | }); 83 | 84 | it('should narrow an unknown type when multiple target keys are found', () => { 85 | if (!has(obj, ['s', 'b'])) { 86 | throw new Error('object should have properties s and b'); 87 | } 88 | // trivial runtime checks but useful for compilation testing 89 | expect(obj.s).to.equal(obj.s); 90 | expect(obj.b).to.equal(obj.b); 91 | }); 92 | }); 93 | 94 | describe('hasString', () => { 95 | it('should not narrow an unknown type when checking a non-string property', () => { 96 | if (hasString(obj, 'b')) { 97 | throw new Error('object should not have string property b'); 98 | } 99 | }); 100 | 101 | it('should narrow an unknown type to an object with a string property', () => { 102 | if (!hasString(obj, 's')) { 103 | throw new Error('object should have string property s'); 104 | } 105 | // trivial runtime check but useful for compilation testing 106 | expect(obj.s).to.equal(obj.s); 107 | }); 108 | }); 109 | 110 | describe('hasNumber', () => { 111 | it('should not narrow an unknown type when checking a non-number property', () => { 112 | if (hasNumber(obj, 's')) { 113 | throw new Error('object should not have number property s'); 114 | } 115 | }); 116 | 117 | it('should narrow an unknown type to an object with a string property', () => { 118 | if (!hasNumber(obj, 'n')) { 119 | throw new Error('object should have number property n'); 120 | } 121 | // trivial runtime check but useful for compilation testing 122 | expect(obj.n).to.equal(obj.n); 123 | }); 124 | }); 125 | 126 | describe('hasBoolean', () => { 127 | it('should not narrow an unknown type when checking a non-string property', () => { 128 | if (hasBoolean(obj, 's')) { 129 | throw new Error('object should not have boolean property s'); 130 | } 131 | }); 132 | 133 | it('should narrow an unknown type to an object with a string property', () => { 134 | if (!hasBoolean(obj, 'b')) { 135 | throw new Error('object should have boolean property b'); 136 | } 137 | // trivial runtime check but useful for compilation testing 138 | expect(obj.b).to.equal(obj.b); 139 | }); 140 | }); 141 | 142 | describe('hasObject', () => { 143 | it('should not narrow an unknown type when checking a non-object property', () => { 144 | if (hasObject(obj, 's')) { 145 | throw new Error('object should not have object property s'); 146 | } 147 | }); 148 | 149 | it('should narrow an unknown type to an object with an object property', () => { 150 | if (!hasObject(obj, 'm')) { 151 | throw new Error('object should have object property m'); 152 | } 153 | // trivial runtime check but useful for compilation testing 154 | expect(obj.m).to.deep.equal(obj.m); 155 | }); 156 | }); 157 | 158 | describe('hasPlainObject', () => { 159 | it('should not narrow an unknown type when checking a non-plain-object property', () => { 160 | if (hasPlainObject(obj, 's')) { 161 | throw new Error('object should not have object property s'); 162 | } 163 | }); 164 | 165 | it('should narrow an unknown type to an object with a plain object property', () => { 166 | if (!hasPlainObject(obj, 'm')) { 167 | throw new Error('object should have plain object property m'); 168 | } 169 | // trivial runtime check but useful for compilation testing 170 | expect(obj.m).to.deep.equal(obj.m); 171 | }); 172 | }); 173 | 174 | describe('hasDictionary', () => { 175 | it('should not narrow an unknown type when checking a non-dictionary property', () => { 176 | if (hasDictionary(obj, 's')) { 177 | throw new Error('object should not have dictionary property s'); 178 | } 179 | }); 180 | 181 | it('should narrow an unknown type to an object with a dictionary property', () => { 182 | if (!hasDictionary(obj, 'm')) { 183 | throw new Error('object should have dictionary property m'); 184 | } 185 | // trivial runtime check but useful for compilation testing 186 | expect(obj.m).to.deep.equal(obj.m); 187 | }); 188 | }); 189 | 190 | describe('hasInstance', () => { 191 | it('should not narrow an unknown type when checking a non-instance property', () => { 192 | if (hasInstance(obj, 's', TestClass)) { 193 | throw new Error('object should not have instance property s'); 194 | } 195 | }); 196 | 197 | it('should narrow an unknown type to an object with an instance property', () => { 198 | if (!hasInstance(obj, 'i', TestClass)) { 199 | throw new Error('object should have instance property i'); 200 | } 201 | // trivial runtime check but useful for compilation testing 202 | expect(obj.i).to.equal(obj.i); 203 | }); 204 | }); 205 | 206 | describe('hasArray', () => { 207 | it('should not narrow an unknown type when checking a non-array property', () => { 208 | if (hasArray(obj, 's')) { 209 | throw new Error('object should not have array property s'); 210 | } 211 | }); 212 | 213 | it('should narrow an unknown type to an object with an array property', () => { 214 | if (!hasArray(obj, 'a')) { 215 | throw new Error('object should have array property a'); 216 | } 217 | // trivial runtime check but useful for compilation testing 218 | expect(obj.a).to.equal(obj.a); 219 | }); 220 | }); 221 | 222 | describe('hasFunction', () => { 223 | it('should not narrow an unknown type when checking a non-function property', () => { 224 | if (hasFunction(obj, 's')) { 225 | throw new Error('object should not have function property s'); 226 | } 227 | }); 228 | 229 | it('should narrow an unknown type to an object with a function property', () => { 230 | if (!hasFunction(obj, 'f')) { 231 | throw new Error('object should have function property f'); 232 | } 233 | // trivial runtime check but useful for compilation testing 234 | expect(obj.f).to.equal(obj.f); 235 | }); 236 | }); 237 | 238 | describe('hasAnyJson', () => { 239 | it('should not narrow an unknown type when checking a non-AnyJson property', () => { 240 | if (hasAnyJson(obj, 'f')) { 241 | throw new Error('object should not have AnyJson property f'); 242 | } 243 | }); 244 | 245 | it('should narrow an unknown type to an object with an AnyJson property with a null value', () => { 246 | if (!hasAnyJson(obj, 'l')) { 247 | throw new Error('object should have AnyJson property l'); 248 | } 249 | expect(obj.l).to.equal(null); 250 | }); 251 | 252 | it('should narrow an unknown type to an object with an AnyJson property with a string value', () => { 253 | if (!hasAnyJson(obj, 's')) { 254 | throw new Error('object should have AnyJson property s'); 255 | } 256 | expect(obj.s).to.equal(''); 257 | }); 258 | 259 | it('should narrow an unknown type to an object with an AnyJson property with a number value', () => { 260 | if (!hasAnyJson(obj, 'n')) { 261 | throw new Error('object should have AnyJson property n'); 262 | } 263 | expect(obj.n).to.equal(0); 264 | }); 265 | 266 | it('should narrow an unknown type to an object with an AnyJson property with a boolean value', () => { 267 | if (!hasAnyJson(obj, 'b')) { 268 | throw new Error('object should have AnyJson property b'); 269 | } 270 | expect(obj.b).to.equal(false); 271 | }); 272 | 273 | it('should narrow an unknown type to an object with an AnyJson with an object value', () => { 274 | if (!hasAnyJson(obj, 'm')) { 275 | throw new Error('object should have AnyJson property m'); 276 | } 277 | expect(obj.m).to.deep.equal({}); 278 | }); 279 | 280 | it('should narrow an unknown type to an object with an AnyJson property with an array value', () => { 281 | if (!hasAnyJson(obj, 'a')) { 282 | throw new Error('object should have AnyJson property a'); 283 | } 284 | expect(obj.a).to.deep.equal([]); 285 | }); 286 | }); 287 | 288 | describe('hasJsonMap', () => { 289 | it('should not narrow an unknown type when checking a non-JsonMap property', () => { 290 | if (!isAnyJson(obj)) { 291 | throw new Error('object must be narrowable to AnyJson'); 292 | } 293 | if (hasJsonMap(obj, 'f')) { 294 | throw new Error('object should not have JsonMap property f'); 295 | } 296 | }); 297 | 298 | it('should narrow an unknown type to an object with a JsonMap property', () => { 299 | if (!isAnyJson(obj)) { 300 | throw new Error('object must be narrowable to AnyJson'); 301 | } 302 | if (!hasJsonMap(obj, 'm')) { 303 | throw new Error('object should have JsonMap property m'); 304 | } 305 | // trivial runtime check but useful for compilation testing 306 | expect(obj.m).to.equal(obj.m); 307 | }); 308 | }); 309 | 310 | describe('hasJsonArray', () => { 311 | it('should not narrow an unknown type when checking a non-JsonArray property', () => { 312 | if (!isAnyJson(obj)) { 313 | throw new Error('object must be narrowable to AnyJson'); 314 | } 315 | if (hasJsonArray(obj, 'f')) { 316 | throw new Error('object should not have JsonArray property f'); 317 | } 318 | }); 319 | 320 | it('should narrow an unknown type to an object with a JsonArray property', () => { 321 | if (!isAnyJson(obj)) { 322 | throw new Error('object must be narrowable to AnyJson'); 323 | } 324 | if (!hasJsonArray(obj, 'a')) { 325 | throw new Error('object should have JsonArray property a'); 326 | } 327 | // trivial runtime check but useful for compilation testing 328 | expect(obj.a).to.equal(obj.a); 329 | }); 330 | }); 331 | }); 332 | -------------------------------------------------------------------------------- /test/narrowing/internal.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { valueOrDefault } from '../../src/narrowing/internal'; 10 | 11 | describe('base', () => { 12 | describe('valueOrDefault', () => { 13 | it('should return undefined when passed an undefined value and an undefined default', () => { 14 | expect(valueOrDefault(undefined, undefined)).to.be.undefined; 15 | }); 16 | 17 | it('should return null when passed a null value and an undefined default', () => { 18 | expect(valueOrDefault(null, undefined)).to.be.null; 19 | }); 20 | 21 | it('should return null when passed an undefined value and a null default', () => { 22 | expect(valueOrDefault(undefined, null)).to.be.null; 23 | }); 24 | 25 | it('should return null when passed a null value and a null default', () => { 26 | expect(valueOrDefault(null, null)).to.be.null; 27 | }); 28 | 29 | it('should return the value when passed a defined value and an undefined default', () => { 30 | expect(valueOrDefault('a', undefined)).to.equal('a'); 31 | }); 32 | 33 | it('should return the value when passed a defined value and a null default', () => { 34 | expect(valueOrDefault('a', null)).to.equal('a'); 35 | }); 36 | 37 | it('should return the value when passed a defined value and a defined default', () => { 38 | expect(valueOrDefault('a', 'b')).to.equal('a'); 39 | }); 40 | 41 | it('should return the default when passed an undefined value and a defined default', () => { 42 | expect(valueOrDefault(undefined, 'b')).to.equal('b'); 43 | }); 44 | 45 | it('should return the default when passed a null value and a defined default', () => { 46 | expect(valueOrDefault(null, 'b')).to.equal('b'); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/narrowing/is.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 9 | /* eslint-disable @typescript-eslint/no-empty-function */ 10 | 11 | import { expect } from 'chai'; 12 | import { 13 | isAnyJson, 14 | isArray, 15 | isArrayLike, 16 | isBoolean, 17 | isClassAssignableTo, 18 | isDictionary, 19 | isFunction, 20 | isInstance, 21 | isJsonArray, 22 | isJsonMap, 23 | isKeyOf, 24 | isNumber, 25 | isObject, 26 | isPlainObject, 27 | isString, 28 | } from '../../src/narrowing/is'; 29 | import { JsonArray } from '../../src/types'; 30 | 31 | class TestClass { 32 | public constructor(public name = 'Test') {} 33 | } 34 | 35 | class TestSubclass extends TestClass { 36 | public constructor(name = 'SubTest') { 37 | super(name); 38 | } 39 | } 40 | 41 | describe('is type', () => { 42 | describe('isString', () => { 43 | it('should return false when passed undefined', () => { 44 | expect(isString(undefined)).to.be.false; 45 | }); 46 | 47 | it('should return true when passed a string', () => { 48 | const value = ''; 49 | expect(isString(value)).to.be.true; 50 | }); 51 | }); 52 | 53 | describe('isNumber', () => { 54 | it('should return false when passed undefined', () => { 55 | expect(isNumber(undefined)).to.be.false; 56 | }); 57 | 58 | it('should return true when passed a number', () => { 59 | const value = 0; 60 | expect(isNumber(value)).to.be.true; 61 | }); 62 | }); 63 | 64 | describe('isBoolean', () => { 65 | it('should return false when passed undefined', () => { 66 | expect(isBoolean(undefined)).to.be.false; 67 | }); 68 | 69 | it('should return true when passed a boolean', () => { 70 | const value = false; 71 | expect(isBoolean(value)).to.be.true; 72 | }); 73 | }); 74 | 75 | describe('isObject', () => { 76 | it('should reject undefined', () => { 77 | expect(isObject(undefined)).to.be.false; 78 | }); 79 | 80 | it('should reject null', () => { 81 | expect(isObject(null)).to.be.false; 82 | }); 83 | 84 | it('should reject string', () => { 85 | expect(isObject('string')).to.be.false; 86 | }); 87 | 88 | it('should reject number', () => { 89 | expect(isObject(1)).to.be.false; 90 | }); 91 | 92 | it('should reject boolean', () => { 93 | expect(isObject(true)).to.be.false; 94 | }); 95 | 96 | it('should accept array', () => { 97 | expect(isObject([])).to.be.true; 98 | }); 99 | 100 | it('should accept object', () => { 101 | expect(isObject({})).to.be.true; 102 | }); 103 | 104 | it('should accept instance of TestClass', () => { 105 | expect(isObject(new TestClass())).to.be.true; 106 | }); 107 | 108 | it('should accept function', () => { 109 | expect(isObject(() => {})).to.be.true; 110 | }); 111 | 112 | it('should accept new String()', () => { 113 | // eslint-disable-next-line no-new-wrappers 114 | expect(isObject(new String('foo'))).to.be.true; 115 | }); 116 | 117 | it('should accept new Number()', () => { 118 | // eslint-disable-next-line no-new-wrappers 119 | expect(isObject(new Number(0))).to.be.true; 120 | }); 121 | 122 | it('should accept new String()', () => { 123 | // eslint-disable-next-line no-new-wrappers 124 | expect(isObject(new Boolean(true))).to.be.true; 125 | }); 126 | 127 | it('should accept new RegExp()', () => { 128 | expect(isObject(new RegExp('foo'))).to.be.true; 129 | }); 130 | }); 131 | 132 | describe('isPlainObject', () => { 133 | it('should reject undefined', () => { 134 | expect(isPlainObject(undefined)).to.be.false; 135 | }); 136 | 137 | it('should reject null', () => { 138 | expect(isPlainObject(null)).to.be.false; 139 | }); 140 | 141 | it('should reject string', () => { 142 | expect(isPlainObject('string')).to.be.false; 143 | }); 144 | 145 | it('should reject number', () => { 146 | expect(isPlainObject(1)).to.be.false; 147 | }); 148 | 149 | it('should reject boolean', () => { 150 | expect(isPlainObject(true)).to.be.false; 151 | }); 152 | 153 | it('should reject array', () => { 154 | expect(isPlainObject([])).to.be.false; 155 | }); 156 | 157 | it('should accept object', () => { 158 | expect(isPlainObject({})).to.be.true; 159 | }); 160 | 161 | it('should reject instance of TestClass', () => { 162 | expect(isPlainObject(new TestClass())).to.be.false; 163 | }); 164 | 165 | it('should reject mock class', () => { 166 | expect(isPlainObject({ constructor: true })).to.be.false; 167 | const wtf = () => {}; 168 | wtf.prototype = TestClass; 169 | expect(isPlainObject({ constructor: wtf })).to.be.false; 170 | }); 171 | 172 | it('should reject function', () => { 173 | expect(isPlainObject(() => {})).to.be.false; 174 | }); 175 | }); 176 | 177 | describe('isDictionary', () => { 178 | it('should reject undefined', () => { 179 | expect(isDictionary(undefined)).to.be.false; 180 | }); 181 | 182 | it('should reject null', () => { 183 | expect(isDictionary(null)).to.be.false; 184 | }); 185 | 186 | it('should reject string', () => { 187 | expect(isDictionary('string')).to.be.false; 188 | }); 189 | 190 | it('should reject number', () => { 191 | expect(isDictionary(1)).to.be.false; 192 | }); 193 | 194 | it('should reject boolean', () => { 195 | expect(isDictionary(true)).to.be.false; 196 | }); 197 | 198 | it('should reject array', () => { 199 | expect(isDictionary([])).to.be.false; 200 | }); 201 | 202 | it('should accept object', () => { 203 | expect(isDictionary({})).to.be.true; 204 | }); 205 | 206 | it('should reject instance of TestClass', () => { 207 | expect(isDictionary(new TestClass())).to.be.false; 208 | }); 209 | 210 | it('should reject mock class', () => { 211 | expect(isDictionary({ constructor: true })).to.be.false; 212 | const wtf = () => {}; 213 | wtf.prototype = TestClass; 214 | expect(isDictionary({ constructor: wtf })).to.be.false; 215 | }); 216 | 217 | it('should reject function', () => { 218 | expect(isDictionary(() => {})).to.be.false; 219 | }); 220 | }); 221 | 222 | describe('isInstance', () => { 223 | it('should reject undefined', () => { 224 | expect(isInstance(undefined, TestClass)).to.be.false; 225 | }); 226 | 227 | it('should reject null', () => { 228 | expect(isInstance(null, TestClass)).to.be.false; 229 | }); 230 | 231 | it('should reject string', () => { 232 | expect(isInstance('string', TestClass)).to.be.false; 233 | }); 234 | 235 | it('should reject number', () => { 236 | expect(isInstance(1, TestClass)).to.be.false; 237 | }); 238 | 239 | it('should reject boolean', () => { 240 | expect(isInstance(true, TestClass)).to.be.false; 241 | }); 242 | 243 | it('should reject array', () => { 244 | expect(isInstance([], TestClass)).to.be.false; 245 | }); 246 | 247 | it('should reject object', () => { 248 | expect(isInstance({}, TestClass)).to.be.false; 249 | }); 250 | 251 | it('should accept instance of TestClass', () => { 252 | expect(isInstance(new TestClass(), TestClass)).to.be.true; 253 | }); 254 | 255 | it('should accept instance of TestSubclass', () => { 256 | expect(isInstance(new TestSubclass(), TestClass)).to.be.true; 257 | }); 258 | 259 | it('should reject function', () => { 260 | expect(isInstance(() => {}, TestClass)).to.be.false; 261 | }); 262 | }); 263 | 264 | describe('isType', () => { 265 | it('should reject undefined', () => { 266 | expect(isClassAssignableTo(undefined, TestClass)).to.be.false; 267 | }); 268 | 269 | it('should reject null', () => { 270 | expect(isClassAssignableTo(null, TestClass)).to.be.false; 271 | }); 272 | 273 | it('should reject string', () => { 274 | expect(isClassAssignableTo('string', TestClass)).to.be.false; 275 | }); 276 | 277 | it('should reject number', () => { 278 | expect(isClassAssignableTo(1, TestClass)).to.be.false; 279 | }); 280 | 281 | it('should reject boolean', () => { 282 | expect(isClassAssignableTo(true, TestClass)).to.be.false; 283 | }); 284 | 285 | it('should reject array', () => { 286 | expect(isClassAssignableTo([], TestClass)).to.be.false; 287 | }); 288 | 289 | it('should reject object', () => { 290 | expect(isClassAssignableTo({}, TestClass)).to.be.false; 291 | }); 292 | 293 | it('should accept TestClass as TestClass', () => { 294 | expect(isClassAssignableTo(TestClass, TestClass)).to.be.true; 295 | }); 296 | 297 | it('should accept TestSubclass as TestClass', () => { 298 | expect(isClassAssignableTo(TestSubclass, TestClass)).to.be.true; 299 | }); 300 | 301 | it('should reject TestClass as TestSubclass', () => { 302 | expect(isClassAssignableTo(TestClass, TestSubclass)).to.be.false; 303 | }); 304 | 305 | it('should reject function', () => { 306 | expect(isClassAssignableTo(() => {}, TestClass)).to.be.false; 307 | }); 308 | }); 309 | 310 | describe('isArray', () => { 311 | it('should reject undefined', () => { 312 | expect(isArray(undefined)).to.be.false; 313 | }); 314 | 315 | it('should reject null', () => { 316 | expect(isArray(null)).to.be.false; 317 | }); 318 | 319 | it('should reject string', () => { 320 | expect(isArray('string')).to.be.false; 321 | }); 322 | 323 | it('should reject number', () => { 324 | expect(isArray(1)).to.be.false; 325 | }); 326 | 327 | it('should reject boolean', () => { 328 | expect(isArray(true)).to.be.false; 329 | }); 330 | 331 | it('should accept array', () => { 332 | expect(isArray([])).to.be.true; 333 | }); 334 | 335 | it('should reject object', () => { 336 | expect(isArray({})).to.be.false; 337 | }); 338 | 339 | it('should reject instance of TestClass', () => { 340 | expect(isArray(new TestClass())).to.be.false; 341 | }); 342 | 343 | it('should reject array-like', () => { 344 | expect(isArray({ length: 1, 0: 'test' })).to.be.false; 345 | }); 346 | 347 | it('should reject function', () => { 348 | expect(isArray(() => {})).to.be.false; 349 | }); 350 | }); 351 | 352 | describe('isArrayLike', () => { 353 | it('should reject undefined', () => { 354 | expect(isArrayLike(undefined)).to.be.false; 355 | }); 356 | 357 | it('should reject null', () => { 358 | expect(isArrayLike(null)).to.be.false; 359 | }); 360 | 361 | it('should accept string', () => { 362 | expect(isArrayLike('string')).to.be.true; 363 | }); 364 | 365 | it('should reject number', () => { 366 | expect(isArrayLike(1)).to.be.false; 367 | }); 368 | 369 | it('should reject boolean', () => { 370 | expect(isArrayLike(true)).to.be.false; 371 | }); 372 | 373 | it('should accept array', () => { 374 | expect(isArrayLike([])).to.be.true; 375 | }); 376 | 377 | it('should reject object', () => { 378 | expect(isArrayLike({})).to.be.false; 379 | }); 380 | 381 | it('should accept array-like', () => { 382 | expect(isArrayLike({ length: 1, 0: 'test' })).to.be.true; 383 | }); 384 | 385 | it('should reject function', () => { 386 | expect(isArrayLike(() => {})).to.be.false; 387 | }); 388 | }); 389 | 390 | describe('isFunction', () => { 391 | it('should reject undefined', () => { 392 | expect(isFunction(undefined)).to.be.false; 393 | }); 394 | 395 | it('should reject null', () => { 396 | expect(isFunction(null)).to.be.false; 397 | }); 398 | 399 | it('should reject string', () => { 400 | expect(isFunction('string')).to.be.false; 401 | }); 402 | 403 | it('should reject number', () => { 404 | expect(isFunction(1)).to.be.false; 405 | }); 406 | 407 | it('should reject boolean', () => { 408 | expect(isFunction(true)).to.be.false; 409 | }); 410 | 411 | it('should reject array', () => { 412 | expect(isFunction([])).to.be.false; 413 | }); 414 | 415 | it('should reject object', () => { 416 | expect(isFunction({})).to.be.false; 417 | }); 418 | 419 | it('should accept function expressions', () => { 420 | // eslint-disable-next-line prefer-arrow-callback 421 | expect(isFunction(function () {})).to.be.true; 422 | }); 423 | 424 | it('should accept arrow functions', () => { 425 | expect(isFunction(() => {})).to.be.true; 426 | }); 427 | }); 428 | 429 | describe('isAnyJson', () => { 430 | it('should return false when passed undefined', () => { 431 | expect(isAnyJson(undefined)).to.be.false; 432 | }); 433 | 434 | it('should return false when passed a function', () => { 435 | expect(isAnyJson(() => {})).to.be.false; 436 | }); 437 | 438 | it('should return true when passed null', () => { 439 | const value = null; 440 | expect(isAnyJson(value)).to.be.true; 441 | }); 442 | 443 | it('should return true when passed a string', () => { 444 | const value = ''; 445 | expect(isAnyJson(value)).to.be.true; 446 | }); 447 | 448 | it('should return true when passed a number', () => { 449 | const value = 0; 450 | expect(isAnyJson(value)).to.be.true; 451 | }); 452 | 453 | it('should return true when passed a boolean', () => { 454 | const value = false; 455 | expect(isAnyJson(value)).to.be.true; 456 | }); 457 | 458 | it('should return true when passed a JsonMap', () => { 459 | const value = {}; 460 | expect(isAnyJson(value)).to.be.true; 461 | }); 462 | 463 | it('should return true when passed a JsonArray', () => { 464 | const value: JsonArray = []; 465 | expect(isAnyJson(value)).to.be.true; 466 | }); 467 | }); 468 | 469 | describe('isJsonMap', () => { 470 | it('should return false when passed undefined', () => { 471 | expect(isJsonMap(undefined)).to.be.false; 472 | }); 473 | 474 | it('should return true when passed a JsonMap', () => { 475 | const value = { a: 'b', c: 'd' }; 476 | expect(isJsonMap(value)).to.be.true; 477 | }); 478 | }); 479 | 480 | describe('isJsonArray', () => { 481 | it('should return false when passed undefined', () => { 482 | expect(isJsonArray(undefined)).to.be.false; 483 | }); 484 | 485 | it('should return true when passed a JsonArray', () => { 486 | const value = ['a', 'b']; 487 | expect(isJsonArray(value)).to.be.true; 488 | }); 489 | }); 490 | 491 | describe('isKeyOf', () => { 492 | it('should return false when passed a non-key string', () => { 493 | expect(isKeyOf({ bar: true }, 'foo')).to.be.false; 494 | }); 495 | 496 | it('should return true when passed a key string', () => { 497 | expect(isKeyOf({ bar: true }, 'bar')).to.be.true; 498 | }); 499 | }); 500 | }); 501 | -------------------------------------------------------------------------------- /test/narrowing/object.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { 10 | definiteEntriesOf, 11 | definiteKeysOf, 12 | definiteValuesOf, 13 | entriesOf, 14 | keysOf, 15 | valuesOf, 16 | } from '../../src/narrowing/object'; 17 | import { Dictionary, Nullable, Optional } from '../../src/types'; 18 | 19 | describe('object', () => { 20 | describe('keysOf', () => { 21 | it('should return an empty array if passed an undefined object', () => { 22 | expect(keysOf(undefined)).to.deep.equal([]); 23 | }); 24 | 25 | it("should allow convenient enumeration of a typed object's keys", () => { 26 | const acc: Array<[string, number]> = []; 27 | type Point = { 28 | x: number; 29 | y: number; 30 | } 31 | const point: Point = { x: 1, y: 2 }; 32 | const keys = keysOf(point); 33 | for (const key of keys) { 34 | acc.push([key, point[key]]); 35 | } 36 | expect(acc).to.deep.equal([ 37 | ['x', 1], 38 | ['y', 2], 39 | ]); 40 | }); 41 | }); 42 | 43 | describe('entriesOf', () => { 44 | it('should return an empty array if passed an undefined object', () => { 45 | expect(entriesOf(undefined)).to.deep.equal([]); 46 | }); 47 | 48 | it("should allow convenient enumeration of a typed object's entries", () => { 49 | const acc: Array<[string, number]> = []; 50 | type Point = { 51 | x: number; 52 | y: number; 53 | } 54 | const point: Point = { x: 1, y: 2 }; 55 | const entries = entriesOf(point); 56 | for (const entry of entries) { 57 | acc.push([entry[0], entry[1]]); 58 | } 59 | expect(acc).to.deep.equal([ 60 | ['x', 1], 61 | ['y', 2], 62 | ]); 63 | }); 64 | }); 65 | 66 | describe('valuesOf', () => { 67 | it('should return an empty array if passed an undefined object', () => { 68 | expect(valuesOf(undefined)).to.deep.equal([]); 69 | }); 70 | 71 | it("should allow convenient enumeration of a typed object's values", () => { 72 | const acc: number[] = []; 73 | type Point = { 74 | x: number; 75 | y: number; 76 | } 77 | const point: Point = { x: 1, y: 2 }; 78 | const values = valuesOf(point); 79 | for (const value of values) { 80 | acc.push(value); 81 | } 82 | expect(acc).to.deep.equal([1, 2]); 83 | }); 84 | }); 85 | 86 | describe('definite *', () => { 87 | type Obj = { 88 | a: string; 89 | b: Optional; 90 | c: Nullable; 91 | } 92 | const obj: Obj = { a: 'foo', b: undefined, c: null }; 93 | const dict: Dictionary> = { 94 | a: 'foo', 95 | b: undefined, 96 | c: null, 97 | }; 98 | 99 | describe('definiteEntriesOf', () => { 100 | it("should allow convenient enumeration of a well-typed object's entries containing definite values", () => { 101 | const entries = definiteEntriesOf(obj); // ['a' | 'b' | 'c', string][] 102 | expect(entries).to.deep.equal([['a', 'foo']]); 103 | }); 104 | 105 | it("should allow convenient enumeration of a dictionary's entries containing definite values", () => { 106 | const entries = definiteEntriesOf(dict); // [string, string][] 107 | expect(entries).to.deep.equal([['a', 'foo']]); 108 | }); 109 | }); 110 | 111 | describe('definiteKeysOf', () => { 112 | it("should allow convenient enumeration of a well-typed object's keys associated with definite values", () => { 113 | const keys = definiteKeysOf(obj); // ('a' | 'b' | 'c')[] 114 | expect(keys).to.deep.equal(['a']); 115 | }); 116 | 117 | it("should allow convenient enumeration of a dictionary's keys associated with definite values", () => { 118 | const keys = definiteKeysOf(dict); // string[] 119 | expect(keys).to.deep.equal(['a']); 120 | }); 121 | }); 122 | 123 | describe('definiteValuesOf', () => { 124 | it("should allow convenient enumeration of a well-typed object's non-nullable values", () => { 125 | const values = definiteValuesOf(obj); // string[] 126 | expect(values).to.deep.equal(['foo']); 127 | }); 128 | 129 | it("should allow convenient enumeration of a dictionary's non-nullable values", () => { 130 | const values = definiteValuesOf(dict); // string[] 131 | expect(values).to.deep.equal(['foo']); 132 | }); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/narrowing/to.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * Licensed under the BSD 3-Clause license. 5 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { expect } from 'chai'; 9 | import { JsonCloneError } from '../../src/errors'; 10 | import { toAnyJson, toJsonArray, toJsonMap } from '../../src/narrowing/to'; 11 | import { Dictionary } from '../../src/types'; 12 | 13 | describe('to type', () => { 14 | describe('toAnyJson', () => { 15 | it('should return undefined when passed undefined', () => { 16 | const value = undefined; 17 | expect(toAnyJson(value)).to.equal(value); 18 | }); 19 | 20 | it('should return a string when passed a string', () => { 21 | const value = ''; 22 | expect(toAnyJson(value)).to.equal(value); 23 | }); 24 | 25 | it('should return a number when passed a number', () => { 26 | const value = 0; 27 | expect(toAnyJson(value)).to.equal(value); 28 | }); 29 | 30 | it('should return a boolean when passed a boolean', () => { 31 | const value = false; 32 | expect(toAnyJson(value)).to.equal(value); 33 | }); 34 | 35 | it('should return an object when passed an object', () => { 36 | const value = { a: 'b', c: 'd' }; 37 | expect(toAnyJson(value)).to.deep.equal(value); 38 | }); 39 | 40 | it('should return an array when passed an array', () => { 41 | const value = ['a', 'b']; 42 | expect(toAnyJson(value)).to.deep.equal(value); 43 | }); 44 | }); 45 | 46 | describe('toJsonMap', () => { 47 | it('should return undefined when passed undefined', () => { 48 | expect(toJsonMap(undefined)).to.be.undefined; 49 | }); 50 | 51 | it('should return a JsonMap when passed a JsonMap', () => { 52 | const value = { a: 'b', c: 'd' }; 53 | expect(toJsonMap(value)).to.deep.equal(value); 54 | }); 55 | 56 | it('should return the default when passed undefined and a default', () => { 57 | const def = { a: 'b', c: 'd' }; 58 | expect(toJsonMap(undefined, def)).to.equal(def); 59 | }); 60 | 61 | it('should omit non-JSON values when passed an object with non-JSON values', () => { 62 | const value = { 63 | a: 'b', 64 | c: 'd', 65 | e: (): void => { 66 | /* do nothing */ 67 | }, 68 | }; 69 | expect(toJsonMap(value)).to.deep.equal({ a: 'b', c: 'd' }); 70 | }); 71 | 72 | it('should throw when passed an object with circular references', () => { 73 | const a: Dictionary = {}; 74 | const b: Dictionary = {}; 75 | a.b = b; 76 | b.a = a; 77 | const value = a; 78 | expect(() => toJsonMap(value)).to.throw(JsonCloneError); 79 | }); 80 | }); 81 | 82 | describe('toJsonArray', () => { 83 | it('should return undefined when passed undefined', () => { 84 | expect(toJsonArray(undefined)).to.be.undefined; 85 | }); 86 | 87 | it('should return a JsonArray when passed a JsonArray', () => { 88 | const value = ['a', 'b']; 89 | expect(toJsonArray(value)).to.deep.equal(value); 90 | }); 91 | 92 | it('should return the default when passed undefined and a default', () => { 93 | const def = ['a', 'b']; 94 | expect(toJsonArray(undefined, def)).to.equal(def); 95 | }); 96 | 97 | it('should omit non-JSON values when passed an array with non-JSON values', () => { 98 | const value = [ 99 | 'a', 100 | null, 101 | 'b', 102 | (): void => { 103 | /* do nothing */ 104 | }, 105 | ]; 106 | expect(toJsonArray(value)).to.deep.equal(['a', null, 'b', null]); 107 | }); 108 | 109 | it('should throw when passed an array with circular references', () => { 110 | const a: Dictionary = {}; 111 | const b: Dictionary = {}; 112 | a.b = b; 113 | b.a = a; 114 | const value = [a, b]; 115 | expect(() => toJsonArray(value)).to.throw(JsonCloneError); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | // Generated - Do not modify. Controlled by @salesforce/dev-scripts 2 | { 3 | "extends": "@salesforce/dev-config/tsconfig-test-strict", 4 | "include": ["./**/*.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Generated - Do not modify. Controlled by @salesforce/dev-scripts 2 | { 3 | "extends": "@salesforce/dev-config/tsconfig-strict", 4 | "compilerOptions": { 5 | "outDir": "lib" 6 | }, 7 | "include": [ 8 | "./src/**/*.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /typedoc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entryPoints: ['./src/index.ts'], 3 | theme: 'default', 4 | }; 5 | --------------------------------------------------------------------------------