├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .github ├── renovate.json5 └── workflows │ ├── ci.yml │ ├── lint.yml │ └── types.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .remarkignore ├── .template-lintrc.js ├── .travis.yml ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon ├── -private │ ├── memoized-task.ts │ ├── task.ts │ ├── types.ts │ └── utils.ts ├── index.ts ├── services │ └── ember-resource-tasks │ │ └── -private │ │ └── do-not-use │ │ └── arg-cache.ts └── tsconfig.json ├── app └── services │ └── ember-resource-tasks │ └── -private │ └── do-not-use │ └── arg-cache.js ├── commitlint.config.js ├── config ├── build │ ├── tsconfig.compiler-options.ember.json │ └── tsconfig.compiler-options.json ├── ember-try.js ├── environment.js └── lint.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.ts │ │ ├── components │ │ │ └── .gitkeep │ │ ├── config │ │ │ └── environment.d.ts │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.ts │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ └── application.hbs │ ├── config │ │ ├── deprecation-workflow.js │ │ ├── ember-cli-update.json │ │ ├── environment.d.ts │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── index.html ├── integration │ └── resources │ │ ├── memoized-task-test.ts │ │ └── task-test.ts ├── test-helper.ts ├── tsconfig.json └── unit │ └── services │ └── ember-source-tasks │ └── -private │ └── do-not-use │ └── arg-cache-test.ts ├── tsconfig.compiler-options.json ├── tsconfig.json ├── types ├── dummy │ └── index.d.ts ├── global.d.ts ├── overrides.d.ts └── untyped-libraries.d.ts ├── vendor └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # typescript output 10 | *.tsbuildinfo 11 | /declarations 12 | /tests/dummy/declarations 13 | 14 | # dependencies 15 | /bower_components/ 16 | /node_modules/ 17 | 18 | # misc 19 | /coverage/ 20 | !.* 21 | .eslintcache 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /package.json.ember-try 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parsers, configs, plugins, rules } = require('./config/lint'); 4 | 5 | const ts = { 6 | plugins: plugins.ts, 7 | extends: configs.ts, 8 | rules: { 9 | ...rules.base, 10 | ...rules.ts, 11 | }, 12 | }; 13 | 14 | const js = { 15 | plugins: plugins.js, 16 | extends: configs.js, 17 | rules: { 18 | ...rules.base, 19 | ...rules.js, 20 | }, 21 | }; 22 | 23 | module.exports = { 24 | root: true, 25 | rules: {}, 26 | overrides: [ 27 | // app 28 | { 29 | files: ['app/**/*.ts', 'types/**'], 30 | ...ts, 31 | }, 32 | // addon 33 | { 34 | files: ['addon/**/*.ts', 'addon-test-support/**/*.ts'], 35 | ...ts, 36 | }, 37 | // tests 38 | { 39 | files: ['tests/**/*.ts'], 40 | ...ts, 41 | rules: { 42 | ...ts.rules, 43 | 44 | // no-op functions are often used for stubbing / testing unrelated to the function things 45 | '@typescript-eslint/no-empty-function': 'off', 46 | }, 47 | }, 48 | // app 49 | { 50 | files: ['app/**/*.js'], 51 | ...js, 52 | }, 53 | // addon 54 | { 55 | files: ['addon/**/*.js', 'addon-test-support/**/*.js'], 56 | ...js, 57 | }, 58 | // tests 59 | { 60 | files: ['tests/**/*.js'], 61 | ...js, 62 | rules: { 63 | ...js.rules, 64 | 65 | // no-op functions are often used for stubbing / testing unrelated to the function things 66 | '@typescript-eslint/no-empty-function': 'off', 67 | }, 68 | }, 69 | // node files 70 | { 71 | files: [ 72 | '.eslintrc.js', 73 | '.template-lintrc.js', 74 | 'ember-cli-build.js', 75 | 'index.js', 76 | 'testem.js', 77 | 'blueprints/*/index.js', 78 | 'config/**/*.js', 79 | 'tests/dummy/config/**/*.js', 80 | ], 81 | excludedFiles: ['addon/**', 'addon-test-support/**', 'app/**', 'tests/dummy/app/**'], 82 | parserOptions: { 83 | sourceType: 'script', 84 | }, 85 | env: { 86 | browser: false, 87 | node: true, 88 | }, 89 | plugins: ['node', 'prettier'], 90 | extends: ['plugin:node/recommended'], 91 | rules: { 92 | ...rules.base, 93 | }, 94 | }, 95 | { 96 | files: ['tests/dummy/config/deprecation-workflow.js'], 97 | env: { 98 | browser: true, 99 | node: false, 100 | }, 101 | }, 102 | // Markdown code samples in documentation 103 | { 104 | files: ['**/*.md'], 105 | ...js, 106 | plugins: [...js.plugins, 'markdown'], 107 | parser: 'babel-eslint', 108 | parserOptions: { 109 | sourceType: 'module', 110 | ecmaFeatures: { legacyDecorators: true }, 111 | }, 112 | rules: { 113 | ...js.rules, 114 | 115 | // Ignore violations that generally don't matter inside code samples. 116 | 'no-console': 'off', 117 | 'no-undef': 'off', 118 | 'no-unused-expressions': 'off', 119 | 'no-unused-labels': 'off', 120 | 'no-unused-vars': 'off', 121 | 'no-useless-constructor': 'off', 122 | 'node/no-missing-import': 'off', 123 | 'node/no-missing-require': 'off', 124 | 'node/no-unsupported-features/es-syntax': 'off', 125 | 'ember/no-test-support-import': 'off', 126 | }, 127 | }, 128 | ], 129 | }; 130 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // Docs: 2 | // https://docs.renovatebot.com/configuration-options/ 3 | { 4 | "extends": [ 5 | "config:base", 6 | ":semanticCommits" 7 | ], 8 | "automerge": true, 9 | "masterIssue": true, 10 | "rangeStrategy": "bump", 11 | "reviewers": [ 12 | "NullVoxPopuli" 13 | ], 14 | "packageRules": [ 15 | //////////////////////////////////////// 16 | // Grouping namespaced packages together 17 | // 18 | // This reduces overall PR count 19 | //////////////////////////////////////// 20 | { 21 | "groupName": "Type Definitions", 22 | "packagePatterns": ["^@types\/*"], 23 | "schedule": ["after 9pm on sunday"], 24 | }, 25 | { 26 | "groupName": "Lint Dependencies", 27 | "schedule": ["after 9pm on sunday"], 28 | "packageNames": [ 29 | "eslint", 30 | "babel-eslint", 31 | "ember-template-lint", 32 | "prettier" 33 | ], 34 | "packagePatterns": [ 35 | "eslint-plugin-.*", 36 | "eslint-config-.*", 37 | ".*typescript-eslint.*", 38 | "^@commitlint\/*", 39 | "^remark-*" 40 | ] 41 | }, 42 | // These are dependencies that come default when 43 | // generating a new ember addon 44 | { 45 | "groupName": "Framework Dependencies", 46 | "packageNames": [ 47 | "@ember/optional-features", 48 | "@glimmer/component", 49 | "@glimmer/tracking", 50 | "ember-disable-prototype-extensions", 51 | "ember-export-application-global", 52 | "ember-load-initializers", 53 | "ember-maybe-import-regenerator", 54 | "ember-resolver", 55 | "ember-source", 56 | "ember-cli-page-title" 57 | ] 58 | }, 59 | { 60 | "groupName": "CLI Dependencies", 61 | "packageNames": [ 62 | "broccoli-asset-rev", 63 | "ember-cli", 64 | "ember-auto-import", 65 | "ember-cli-dependency-checker", 66 | "ember-cli-inject-live-reload", 67 | "ember-cli-sri", 68 | "ember-cli-terser" 69 | ] 70 | }, 71 | { 72 | "groupName": "Testing Dependencies", 73 | "schedule": ["after 9pm on sunday"], 74 | "packageNames": [ 75 | "qunit-dom", 76 | "ember-try", 77 | "ember-source-channel-url", 78 | "ember-qunit", 79 | "qunit", 80 | "npm-run-all" 81 | ] 82 | }, 83 | { 84 | // changing peerDependencies *at all* is a breaking change 85 | "matchDepTypes": ["peerDependencies"], 86 | "enabled": false 87 | }, 88 | { 89 | // changing engines forces other people to need to upgrade their minimum node 90 | // therefor engine changes are breaking changes 91 | "depTypeList": ["engines"], 92 | "enabled": false 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: "0 3 * * 0" # every Sunday at 3am 11 | 12 | env: 13 | CI: true 14 | 15 | jobs: 16 | tests: 17 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 18 | name: Base Tests 19 | timeout-minutes: 5 20 | runs-on: ubuntu-latest 21 | strategy: 22 | matrix: 23 | node: 24 | - "12" 25 | - "14" 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: volta-cli/action@v1 29 | with: 30 | node-version: ${{ matrix.node }} 31 | 32 | - run: yarn install --frozen-lockfile 33 | 34 | - name: Test with ${{ matrix.node }} 35 | run: yarn ember test 36 | 37 | floating-dependencies: 38 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 39 | name: Floating Dependencies 40 | timeout-minutes: 5 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | node: 45 | - "12" 46 | - "14" 47 | 48 | steps: 49 | - uses: actions/checkout@v2 50 | - uses: volta-cli/action@v1 51 | with: 52 | node-version: ${{ matrix.node }} 53 | 54 | - run: yarn install --no-lockfile 55 | 56 | - name: Test with Node ${{ matrix.node }} 57 | run: yarn ember test 58 | 59 | try-scenarios: 60 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 61 | name: "Compatibility" 62 | timeout-minutes: 7 63 | runs-on: ubuntu-latest 64 | needs: tests 65 | 66 | strategy: 67 | fail-fast: true 68 | matrix: 69 | node: 70 | - "12" 71 | - "14" 72 | ember-try-scenario: 73 | - ember-lts-3.24 74 | - ember-lts-3.28 75 | - ember-release 76 | - ember-beta 77 | - ember-canary 78 | - ember-concurrency-1.x 79 | - ember-concurrency-2.x 80 | - embroider-safe 81 | - embroider-optimized 82 | steps: 83 | - uses: actions/checkout@v2 84 | - uses: volta-cli/action@v1 85 | with: 86 | node-version: ${{ matrix.node }} 87 | - name: install dependencies 88 | run: yarn install --no-lockfile 89 | - name: test 90 | run: node_modules/.bin/ember try:one ${{ matrix.ember-try-scenario }} --skip-cleanup 91 | 92 | publish: 93 | name: Release 94 | runs-on: ubuntu-latest 95 | if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' 96 | needs: [tests, try-scenarios, floating-dependencies] 97 | 98 | steps: 99 | - uses: actions/checkout@v2 100 | with: 101 | persist-credentials: false 102 | - uses: volta-cli/action@v1 103 | - run: yarn install 104 | 105 | - name: Release 106 | run: yarn semantic-release 107 | env: 108 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 109 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 110 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | # based on: 4 | # - https://github.com/NullVoxPopuli/eslint-plugin-decorator-position/blob/master/.github/workflows/lint.yml 5 | # - https://github.com/NullVoxPopuli/ember-autostash-modifier/blob/master/.github/workflows/ci.yml 6 | # - https://github.com/emberjs/ember-test-helpers/blob/master/.github/workflows/ci-build.yml 7 | on: 8 | pull_request: 9 | push: 10 | # filtering branches here prevents duplicate builds from pull_request and push 11 | branches: 12 | - main 13 | 14 | env: 15 | CI: true 16 | 17 | jobs: 18 | source: 19 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 20 | name: Source 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: volta-cli/action@v1 26 | 27 | - run: yarn install --frozen-lockfile 28 | 29 | - name: ESLint 30 | run: yarn lint:js 31 | 32 | - name: Templates 33 | run: yarn lint:hbs 34 | 35 | tooling: 36 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 37 | name: Tooling 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: volta-cli/action@v1 43 | - run: yarn install --frozen-lockfile 44 | - name: Semantic Release 45 | run: yarn semantic-release --dry-run 46 | env: 47 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | 51 | 52 | docs: 53 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 54 | name: Docs 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: volta-cli/action@v1 60 | 61 | - run: yarn install 62 | - run: yarn lint:docs 63 | 64 | 65 | docs-js-code: 66 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 67 | name: Docs (JS Code Samples) 68 | runs-on: ubuntu-latest 69 | 70 | steps: 71 | - uses: actions/checkout@v2 72 | - uses: volta-cli/action@v1 73 | 74 | - run: yarn install 75 | - run: yarn lint:docs-js 76 | 77 | commits: 78 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 79 | name: Commit Messages 80 | runs-on: ubuntu-latest 81 | 82 | steps: 83 | - uses: actions/checkout@v2 84 | with: 85 | fetch-depth: 0 86 | 87 | - uses: volta-cli/action@v1 88 | - uses: wagoid/commitlint-github-action@v4.1.9 89 | -------------------------------------------------------------------------------- /.github/workflows/types.yml: -------------------------------------------------------------------------------- 1 | name: Types 2 | 3 | # based on: 4 | # - https://github.com/NullVoxPopuli/eslint-plugin-decorator-position/blob/master/.github/workflows/lint.yml 5 | # - https://github.com/NullVoxPopuli/ember-autostash-modifier/blob/master/.github/workflows/ci.yml 6 | # - https://github.com/emberjs/ember-test-helpers/blob/master/.github/workflows/ci-build.yml 7 | on: 8 | pull_request: 9 | push: 10 | # filtering branches here prevents duplicate builds from pull_request and push 11 | branches: 12 | - main 13 | 14 | env: 15 | CI: true 16 | 17 | jobs: 18 | types: 19 | if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" 20 | name: Type Checking 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: volta-cli/action@v1 26 | 27 | - run: yarn install --frozen-lockfile 28 | 29 | - name: Type Checking 30 | run: yarn tsc --build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # typescript output 8 | *.tsbuildinfo 9 | /declarations 10 | /tests/dummy/declarations 11 | 12 | # dependencies 13 | /bower_components/ 14 | /node_modules/ 15 | 16 | # misc 17 | /.eslintcache 18 | /.env* 19 | /.pnp* 20 | /.sass-cache 21 | /.eslintcache 22 | /connect.lock 23 | /coverage/ 24 | /libpeerconnection.log 25 | /npm-debug.log* 26 | /testem.log 27 | /yarn-error.log 28 | 29 | # ember-try 30 | /.node_modules.ember-try/ 31 | /bower.json.ember-try 32 | /package.json.ember-try 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.gitignore 18 | /.prettierignore 19 | /.prettierrc.js 20 | /.template-lintrc.js 21 | /.travis.yml 22 | /.watchmanconfig 23 | /bower.json 24 | /config/ember-try.js 25 | /CONTRIBUTING.md 26 | /ember-cli-build.js 27 | /testem.js 28 | /tests/ 29 | /yarn.lock 30 | .gitkeep 31 | 32 | # ember-try 33 | /.node_modules.ember-try/ 34 | /bower.json.ember-try 35 | /package.json.ember-try 36 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org 2 | ignore-scripts = false 3 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | LICENSE.md 3 | 4 | node_modules/ 5 | tmp/ 6 | dist/ 7 | vendor/ 8 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'octane', 5 | }; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "10" 7 | 8 | dist: xenial 9 | 10 | addons: 11 | chrome: stable 12 | 13 | cache: 14 | yarn: true 15 | 16 | env: 17 | global: 18 | # See https://git.io/vdao3 for details. 19 | - JOBS=1 20 | 21 | branches: 22 | only: 23 | - master 24 | # npm version tags 25 | - /^v\d+\.\d+\.\d+/ 26 | 27 | jobs: 28 | fast_finish: true 29 | allow_failures: 30 | - env: EMBER_TRY_SCENARIO=ember-canary 31 | 32 | include: 33 | # runs linting and tests with current locked deps 34 | - stage: "Tests" 35 | name: "Tests" 36 | script: 37 | - yarn lint 38 | - yarn test:ember 39 | 40 | - stage: "Additional Tests" 41 | name: "Floating Dependencies" 42 | install: 43 | - yarn install --no-lockfile --non-interactive 44 | script: 45 | - yarn test:ember 46 | 47 | # we recommend new addons test the current and previous LTS 48 | # as well as latest stable release (bonus points to beta/canary) 49 | - env: EMBER_TRY_SCENARIO=ember-lts-3.16 50 | - env: EMBER_TRY_SCENARIO=ember-lts-3.20 51 | - env: EMBER_TRY_SCENARIO=ember-release 52 | - env: EMBER_TRY_SCENARIO=ember-beta 53 | - env: EMBER_TRY_SCENARIO=ember-canary 54 | - env: EMBER_TRY_SCENARIO=ember-default-with-jquery 55 | - env: EMBER_TRY_SCENARIO=ember-classic 56 | - env: EMBER_TRY_SCENARIO=embroider-safe 57 | - env: EMBER_TRY_SCENARIO=embroider-optimized 58 | 59 | before_install: 60 | - curl -o- -L https://yarnpkg.com/install.sh | bash 61 | - export PATH=$HOME/.yarn/bin:$PATH 62 | 63 | script: 64 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 65 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.6](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.5...v2.0.6) (2022-02-04) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **deps:** update dependency ember-cli-typescript to v5 ([70fdd41](https://github.com/CrowdStrike/ember-resource-tasks/commit/70fdd411dac4122290249435ed3de78192f421a6)) 7 | 8 | ## [2.0.5](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.4...v2.0.5) (2022-01-26) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** update dependency ember-cli-babel to ^7.26.11 ([3b3c099](https://github.com/CrowdStrike/ember-resource-tasks/commit/3b3c0995fe28d96421aa93f85825cd8e2a7c41a2)) 14 | * **ember-concurrency:** widen support ([e3ed8c1](https://github.com/CrowdStrike/ember-resource-tasks/commit/e3ed8c13c0eac62b746ed6a84f9dc17537bd074c)) 15 | 16 | ## [2.0.4](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.3...v2.0.4) (2021-12-17) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * **deps:** update dependency ember-cli-babel to ^7.26.10 ([5fab650](https://github.com/CrowdStrike/ember-resource-tasks/commit/5fab65062029fcc1559524e0b6e5a18a74b51a9d)) 22 | 23 | ## [2.0.3](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.2...v2.0.3) (2021-12-16) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * **deps:** update dependency ember-cli-babel to ^7.26.8 ([67283ef](https://github.com/CrowdStrike/ember-resource-tasks/commit/67283ef0896e56c5c4f9b3cd24d19b727ef43d45)) 29 | 30 | ## [2.0.2](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.1...v2.0.2) (2021-12-05) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * **deps:** update dependency ember-cli-htmlbars to ^6.0.1 ([9ddbe3d](https://github.com/CrowdStrike/ember-resource-tasks/commit/9ddbe3deb77ca55ade5b5ec42db57aaddb0d839a)) 36 | 37 | ## [2.0.1](https://github.com/CrowdStrike/ember-resource-tasks/compare/v2.0.0...v2.0.1) (2021-11-23) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * **readme:** bump minimum supportetd embroider (still pre 1.0) ([6fa02a3](https://github.com/CrowdStrike/ember-resource-tasks/commit/6fa02a3376ea59d23f10ee0359199776a05b397e)) 43 | 44 | # [2.0.0](https://github.com/CrowdStrike/ember-resource-tasks/compare/v1.1.1...v2.0.0) (2021-04-28) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * **ts:** support TS4.2 and latest @types/ember ([f9f11a3](https://github.com/CrowdStrike/ember-resource-tasks/commit/f9f11a35662aa9c7461bd5f4bc8486f39e66022b)) 50 | 51 | 52 | ### BREAKING CHANGES 53 | 54 | * **ts:** public api has narrowed. 55 | see: addon/-private/types.ts 56 | 57 | ## [1.1.1](https://github.com/CrowdStrike/ember-resource-tasks/compare/v1.1.0...v1.1.1) (2021-04-23) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * **memoized-task:** now canceled tasks are automatically retried ([0ce0373](https://github.com/CrowdStrike/ember-resource-tasks/commit/0ce0373b90da7658de416f7c4dfa781f252cd266)) 63 | 64 | # [1.1.0](https://github.com/CrowdStrike/ember-resource-tasks/compare/v1.0.0...v1.1.0) (2021-03-15) 65 | 66 | 67 | ### Features 68 | 69 | * support embroider ([95d650c](https://github.com/CrowdStrike/ember-resource-tasks/commit/95d650c9e181dfa5e3c27b1afbb75ed1a4602d14)) 70 | 71 | # 1.0.0 (2021-03-09) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * ember-concurrency@v2 support ([97055d2](https://github.com/CrowdStrike/ember-resource-tasks/commit/97055d20488ce5312de8a995ce20312143349747)) 77 | * **readme:** make the cacheKey blurb more clear / fix typo ([748b394](https://github.com/CrowdStrike/ember-resource-tasks/commit/748b394294b892ae0392fefae8c1def26785673a)) 78 | 79 | 80 | ### Features 81 | 82 | * figure out how to test Resources in pure JS ([1ca2214](https://github.com/CrowdStrike/ember-resource-tasks/commit/1ca2214787d58e376b6c1e2727b7d4dc2393942a)) 83 | * initial implementation ([b80f006](https://github.com/CrowdStrike/ember-resource-tasks/commit/b80f006bc604af0f23f96f64ced78321d1fdcc70)) 84 | 85 | 86 | ### BREAKING CHANGES 87 | 88 | * initial implementation 89 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-resource-tasks` 7 | * `yarn install` 8 | 9 | ## Linting 10 | 11 | * `yarn lint` 12 | * `yarn lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ### Running tests for an ember-try scenario: 21 | 22 | ```bash 23 | # ember try:one --- 24 | ember try:one ember-concurrency-2.x --- ember s 25 | ``` 26 | then visit http://localhost:4200/tests 27 | 28 | ## Running the dummy application 29 | 30 | * `ember serve` 31 | * Visit the dummy application at . 32 | 33 | For more information on using ember-cli, visit . 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-resource-tasks 2 | 3 | [![CI](https://github.com/CrowdStrike/ember-resource-tasks/actions/workflows/ci.yml/badge.svg)](https://github.com/CrowdStrike/ember-resource-tasks/actions/workflows/ci.yml) 4 | [![npm version](https://badge.fury.io/js/ember-resource-tasks.svg)](https://badge.fury.io/js/ember-resource-tasks) 5 | 6 | `ember-resource-tasks` is a collection of utility [Resources](https://github.com/pzuraq/ember-could-get-used-to-this#resources) for interacting with 7 | async data. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | yarn add ember-resource-tasks 13 | # or 14 | npm install ember-resource-tasks 15 | # or 16 | ember install ember-resource-tasks 17 | ``` 18 | 19 | ## Compatibility 20 | 21 | - Ember.js v3.24 or above 22 | - Node.js v12 or above 23 | - ember-concurrency v1+ and v2+ 24 | - Embroider 0.47.2+ 25 | 26 | ## Usage 27 | 28 | ### Reactive async data in vanilla JavaScript class 29 | 30 | A common problem in Frontend JS is knowing when to re-invoke async 31 | functions when args change. With Glimmer's auto-tracking and Resources, 32 | async functions can automatically be kept up to date as the tracked args 33 | to the function change. 34 | 35 | For example, given this vanilla JavaScript class with two tracked properties: 36 | 37 | ```ts 38 | import { tracked } from '@glimmer/tracking'; 39 | import { use } from 'ember-could-get-used-to-this'; 40 | import { action } from '@ember/object'; 41 | import { Task } from 'ember-resource-tasks'; 42 | 43 | class MyComponent { 44 | @use data = new Task(() => { 45 | return { 46 | named: { 47 | args: [this.argA, this.argB] 48 | fn: async (argA, argB) => { 49 | 50 | }, 51 | } 52 | } 53 | }); 54 | 55 | @tracked argA = 0; 56 | @tracked argB = 0; 57 | } 58 | ``` 59 | 60 | Whenever changes to `argA` or `argB` occur, the 61 | [ember-concurrency](http://ember-concurrency.com) task that wraps the passed 62 | `fn` function will be re-performed. 63 | 64 | Example invocation: 65 | 66 | ```ts 67 | // Instantiate the Class 68 | let service = new MyService(); 69 | // get owner from other container-aware context 70 | setOwner(owner, service); 71 | 72 | // task begins 73 | service.data; 74 | 75 | // some time later, the value is populated and can be rendered 76 | // in a test, you may `await settled()` to get here 77 | console.log(service.data.value); 78 | ``` 79 | 80 | ### Usage from a component 81 | 82 | ```ts 83 | import Component from '@glimmer/component'; 84 | import { tracked } from '@glimmer/tracking'; 85 | import { use } from 'ember-could-get-used-to-this'; 86 | import { action } from '@ember/object'; 87 | import { Task } from 'ember-resource-tasks'; 88 | 89 | export default class MyComponent extends Component { 90 | @tracked query = 'Luke Skywalker'; 91 | 92 | @use data = new Task(() => { 93 | return { 94 | named: { 95 | args: [this.query] 96 | fn: this.search, 97 | } 98 | } 99 | }); 100 | 101 | @action 102 | search(query) { /* ... performs search on https://swapi.dev/ ... */ } 103 | 104 | // ... other code that debounces updates to the query property 105 | } 106 | ``` 107 | ```hbs 108 | {{#if this.data.value}} 109 | Results: {{this.data.value}} 110 | {{/if}} 111 | ``` 112 | 113 | When the template is rendered, the task will run and fetch initial data. 114 | Any changes to the `query` will cause `search` to be re-invoked, and 115 | `this.data.value` will be re-updated when `search` finishes. 116 | 117 | ### Caching 118 | 119 | Sometimes you may want to cache Task results if there is a UI you're implementing 120 | where multiple sets of `args` can be toggled back and forth and you'd like to 121 | avoid re-hitting your API when you've already fetched the data. 122 | 123 | There is the `MemoizedTask`, which handles the cache for you. 124 | The usage is the exact same as `Task`, except the import name is different: 125 | 126 | ```ts 127 | import { MemoizedTask } from 'ember-resource-task'; 128 | ``` 129 | 130 | and there is an additional arg that it takes, `cacheKey`. 131 | This `cacheKey` is used to categorize your data, so that multiple instances of 132 | `MemoizedTask` may share data. The `cacheKey` is kind of like a "bucket" that the 133 | cache for all fn/args pairs are grouped in to, so if you happen to have the same 134 | args between two MemoizedTasks, but different `cacheKey`s, they will not overwrite 135 | each other. 136 | 137 | Example: 138 | 139 | ```ts 140 | @use cached = new MemoizedTask(() => { 141 | return { 142 | named: { 143 | cacheKey: 'contacts', // or any string 144 | args: [this.someTrackedArg], 145 | fn: (someTrackedArgValue) => { ... } 146 | } 147 | } 148 | }); 149 | ``` 150 | 151 | 152 | ### TypeScript 153 | 154 | ember-resource-tasks provides a no-op type helper for helping TypeScript 155 | become aware of the return value provided by `@use`. 156 | 157 | ```ts 158 | import { valueFor } from 'ember-resource-tasks'; 159 | 160 | // ... 161 | 162 | @use data = valueFor(new Task(() => [ /* ... */ ])); 163 | ``` 164 | 165 | the return type if both `@use Task` and `@use MemoizedTask` is a 166 | [TaskInstance](https://ember-concurrency.com/api/TaskInstance.html) 167 | with a `retry` method. 168 | 169 | ## Contributing 170 | 171 | See the [Contributing](CONTRIBUTING.md) guide for details. 172 | 173 | ## License 174 | 175 | This project is licensed under the [MIT License](LICENSE.md). 176 | -------------------------------------------------------------------------------- /addon/-private/memoized-task.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from 'ember-could-get-used-to-this'; 2 | import { action } from '@ember/object'; 3 | import { assert } from '@ember/debug'; 4 | import { getOwner } from '@ember/application'; 5 | import { DEBUG } from '@glimmer/env'; 6 | 7 | import { taskFor } from 'ember-concurrency-ts'; 8 | import { task } from 'ember-concurrency-decorators'; 9 | import { consumeTag, extractTaskData, toCacheKey, waitFor } from './utils'; 10 | 11 | import type { TaskInstance, TaskGenerator } from 'ember-concurrency'; 12 | import type { PublicAPI } from './types'; 13 | 14 | type CacheableArgs = Array; 15 | 16 | interface Args { 17 | named: { 18 | cacheKey: string; 19 | args: TaskArgs; 20 | fn: (...args: TaskArgs) => Promise; 21 | }; 22 | } 23 | 24 | const CACHE = 'service:ember-resource-tasks/-private/do-not-use/arg-cache'; 25 | 26 | export const TASK_PROPERTY = Symbol('TASK'); 27 | export const TASK_INSTANCE_FROM_CACHE = Symbol('TASK_FROM_CACHE'); 28 | 29 | /** 30 | * Whenever any args change *value*, the task will re run 31 | * All args must be stringish 32 | */ 33 | export class MemoizedTask extends Resource< 34 | Args 35 | > { 36 | private declare cacheBucket: Map>; 37 | 38 | /** 39 | * @public 40 | * 41 | * This is the return value of a resource 42 | */ 43 | get value(): PublicAPI { 44 | let task = this.cacheBucket.get(this.cacheKey); 45 | 46 | assert(`A task failed to start`, task); 47 | 48 | // entangle with the task's properties when it resolves 49 | consumeTag(task, 'value'); 50 | consumeTag(task, 'isFinished'); 51 | consumeTag(task, 'error'); 52 | 53 | let result = { ...extractTaskData(task), retry: this._perform }; 54 | 55 | if (DEBUG) { 56 | (result as any)[TASK_INSTANCE_FROM_CACHE] = task; 57 | (result as any)[TASK_PROPERTY] = task; 58 | } 59 | 60 | return result; 61 | } 62 | 63 | private get cacheKey() { 64 | return toCacheKey(...this.args.named.args); 65 | } 66 | 67 | private get needsUpdate() { 68 | if (!this.cacheBucket.has(this.cacheKey)) { 69 | return true; 70 | } 71 | 72 | return this.cacheBucket.get(this.cacheKey)?.isCanceled; 73 | } 74 | 75 | get _task() { 76 | return taskFor(this.__task); 77 | } 78 | 79 | @task 80 | @waitFor 81 | *__task(this: MemoizedTask): TaskGenerator { 82 | let { fn, args } = this.args.named; 83 | 84 | // Because async functions can set tracked data during rendering, (before an await is hit in execution) 85 | // we are presented with this assertion: 86 | // 87 | // Error: Assertion Failed: You attempted to update `someProperty` on ``, 88 | // but it had already been used previously in the same computation. 89 | // Attempting to update a value after using it in a computation can cause logical errors, 90 | // infinite revalidation bugs, and performance issues, and is not supported. 91 | yield Promise.resolve(); 92 | 93 | let result = yield fn(...args); 94 | 95 | return result; 96 | } 97 | 98 | @action 99 | _perform() { 100 | let task = this._task.perform(); 101 | 102 | this.cacheBucket.set(this.cacheKey, task); 103 | } 104 | 105 | /** 106 | * Resource lifecycle methods 107 | */ 108 | setup() { 109 | this.cacheBucket = getOwner(this).lookup(CACHE).getEnsuringBucket(this.args.named.cacheKey); 110 | 111 | if (this.needsUpdate) { 112 | this._perform(); 113 | } 114 | } 115 | 116 | update() { 117 | if (this.needsUpdate) { 118 | this._perform(); 119 | } 120 | } 121 | 122 | teardown() { 123 | this._task.cancelAll(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /addon/-private/task.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from 'ember-could-get-used-to-this'; 2 | import { action } from '@ember/object'; 3 | import { assert } from '@ember/debug'; 4 | 5 | import { taskFor } from 'ember-concurrency-ts'; 6 | import { task } from 'ember-concurrency-decorators'; 7 | 8 | import { consumeTag, waitFor, extractTaskData } from './utils'; 9 | 10 | import type { TaskGenerator } from 'ember-concurrency'; 11 | import type { PublicAPI } from './types'; 12 | 13 | interface Args { 14 | named: { 15 | args: TaskArgs; 16 | fn: (...args: TaskArgs) => Promise; 17 | }; 18 | } 19 | 20 | /** 21 | * Whenever any args change *value*, the task will re run 22 | * All args must be stringish 23 | */ 24 | export class Task extends Resource> { 25 | /** 26 | * @public 27 | * 28 | * This is the return value of a resource 29 | */ 30 | get value(): PublicAPI { 31 | let task = this._task.last; 32 | 33 | assert(`A task failed to start`, task); 34 | 35 | // entangle with the task's properties when it resolves 36 | consumeTag(task, 'value'); 37 | consumeTag(task, 'isFinished'); 38 | consumeTag(task, 'error'); 39 | 40 | return { ...extractTaskData(task), retry: this._perform }; 41 | } 42 | 43 | get _task() { 44 | return taskFor(this.__task); 45 | } 46 | 47 | @task 48 | @waitFor 49 | *__task(this: Task): TaskGenerator { 50 | let { fn, args } = this.args.named; 51 | 52 | // Because async functions can set tracked data during rendering, (before an await is hit in execution) 53 | // we are presented with this assertion: 54 | // 55 | // Error: Assertion Failed: You attempted to update `someProperty` on ``, 56 | // but it had already been used previously in the same computation. 57 | // Attempting to update a value after using it in a computation can cause logical errors, 58 | // infinite revalidation bugs, and performance issues, and is not supported. 59 | yield Promise.resolve(); 60 | 61 | let result = yield fn(...args); 62 | 63 | return result; 64 | } 65 | 66 | @action 67 | _perform() { 68 | this._task.perform(); 69 | } 70 | 71 | /** 72 | * Resource lifecycle methods 73 | */ 74 | setup() { 75 | this._perform(); 76 | } 77 | 78 | update() { 79 | this._perform(); 80 | } 81 | 82 | teardown() { 83 | this._task.cancelAll(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /addon/-private/types.ts: -------------------------------------------------------------------------------- 1 | import type { TaskInstance } from 'ember-concurrency'; 2 | 3 | export interface PublicAPI { 4 | retry: () => void; 5 | value: TaskInstance['value']; 6 | error: TaskInstance['error']; 7 | 8 | isRunning: TaskInstance['isRunning']; 9 | isCanceled: TaskInstance['isCanceled']; 10 | isError: TaskInstance['isError']; 11 | isFinished: TaskInstance['isFinished']; 12 | isSuccessful: TaskInstance['isSuccessful']; 13 | hasStarted: TaskInstance['hasStarted']; 14 | } 15 | -------------------------------------------------------------------------------- /addon/-private/utils.ts: -------------------------------------------------------------------------------- 1 | import { waitFor as _waitFor } from '@ember/test-waiters'; 2 | import type { TaskInstance } from 'ember-concurrency'; 3 | import type { Resource } from 'ember-could-get-used-to-this'; 4 | import type { PublicAPI } from './types'; 5 | 6 | export { get as consumeTag } from '@ember/object'; 7 | 8 | export const waitFor = (_waitFor as unknown) as PropertyDecorator; 9 | 10 | export function toCacheKey(...tokens: Array) { 11 | return tokens.flat().join('-'); 12 | } 13 | 14 | /** 15 | * No-op TypeScript helper for helping reshape the type of the Resource in TypeScript files 16 | */ 17 | export function valueFor>(instance: SomeResource) { 18 | return (instance as unknown) as SomeResource['value']; 19 | } 20 | 21 | /** 22 | * NOTE: some properties on a task are not iterable, therefore not included in the spread 23 | * This is probably fine, for the most part. 24 | * This was more of an issue for ember-concurrency@v2 support though 25 | */ 26 | export function extractTaskData( 27 | task: TaskInstance, 28 | ): Omit, 'retry'> { 29 | return { 30 | ...task, 31 | value: task.value, 32 | isRunning: task.isRunning, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /addon/index.ts: -------------------------------------------------------------------------------- 1 | export { Task } from './-private/task'; 2 | export { MemoizedTask } from './-private/memoized-task'; 3 | export { valueFor } from './-private/utils'; 4 | -------------------------------------------------------------------------------- /addon/services/ember-resource-tasks/-private/do-not-use/arg-cache.ts: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | 3 | /** 4 | * This service is basically just Map> 5 | * - this cache exists as a service so it can be cleaned up during tests 6 | * - used by MemoizedTask where args must be stringifyable 7 | * as they are joined together to use as a key to the map. 8 | * at some point, we may want a dynamically deep Map 9 | * so that the stringifyability doesn't matter, and then 10 | * any args could be passed to the MemoizedTask fnArgs 11 | * 12 | */ 13 | export default class ArgCache extends Service { 14 | private _cache = new Map>(); 15 | 16 | getEnsuringBucket(cacheName: string) { 17 | let bucket = this._cache.get(cacheName); 18 | 19 | if (!bucket) { 20 | bucket = new Map(); 21 | this._cache.set(cacheName, bucket); 22 | } 23 | 24 | return bucket; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /addon/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.compiler-options.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declarationDir": "../declarations", 6 | "paths": { 7 | "ember-resource-tasks": ["."], 8 | "ember-resource-tasks/*": ["./*"], 9 | "*": ["../types"] 10 | } 11 | }, 12 | "include": [".", "../types"] 13 | } 14 | -------------------------------------------------------------------------------- /app/services/ember-resource-tasks/-private/do-not-use/arg-cache.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-resource-tasks/services/ember-resource-tasks/-private/do-not-use/arg-cache'; 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { extends: ['@commitlint/config-conventional'] }; 4 | -------------------------------------------------------------------------------- /config/build/tsconfig.compiler-options.ember.json: -------------------------------------------------------------------------------- 1 | // https://www.typescriptlang.org/v2/en/tsconfig 2 | { 3 | "$schema": "http://json.schemastore.org/tsconfig", 4 | "extends": "./tsconfig.compiler-options.json", 5 | "compilerOptions": { 6 | // allows for smaller partial tsconfigs where configuration is more relevant 7 | "composite": true, 8 | 9 | // declarations are dumped to their own directory 10 | // which is gitignored, and shouldn't interfere with commits 11 | "emitDeclarationOnly": true, 12 | "noEmit": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /config/build/tsconfig.compiler-options.json: -------------------------------------------------------------------------------- 1 | // https://www.typescriptlang.org/v2/en/tsconfig 2 | { 3 | "compilerOptions": { 4 | // Babel handles transpiling, not tsc 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "experimentalDecorators": true, 8 | 9 | // Many npm modules are not distributed as actual modules 10 | "allowSyntheticDefaultImports": true, 11 | 12 | // Some of these overlap with @typescript-eslint a bit, but we can fail faster 13 | // by also having the type checker check these (tightens the feedback loop). 14 | // 15 | // -- cleanliness 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": false, 19 | // -- correctness 20 | "noImplicitAny": true, 21 | "noImplicitThis": true, 22 | "alwaysStrict": true, 23 | "strictNullChecks": true, 24 | "importsNotUsedAsValues": "error", // enforces type imports when imports are not used as values 25 | // -- footgun prevention 26 | "strictPropertyInitialization": true, 27 | "noFallthroughCasesInSwitch": true, 28 | 29 | // noEmitOnError will "break the build" like a traditional statically typed language 30 | "noEmitOnError": false, 31 | 32 | // default to not transpiling to js every save 33 | // "noEmit": false, 34 | 35 | // forward source maps to babel 36 | "inlineSourceMap": true, 37 | "inlineSources": true, 38 | 39 | // Full Typescript library 40 | "allowJs": false, 41 | // checkJs conflicts with noImplicitAny 42 | "checkJs": false, 43 | 44 | // Most bundlers now-a-days have node/node-like module resolution 45 | "moduleResolution": "node", 46 | 47 | // NOTE: specifying "paths" here is not inherited by TS projects 48 | // as the "extends" option does not resolve paths when the baseUrl changes. 49 | // 50 | // Each ts project will have some duplication with all the upward relative paths 51 | // referencing addons within the monorepo. 52 | // "paths": {}, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | useYarn: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-3.24', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~3.24.2', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-3.28', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~3.28.2', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | dependencies: { 30 | 'ember-auto-import': '^2.0.0', 31 | }, 32 | devDependencies: { 33 | 'ember-source': await getChannelURL('release'), 34 | webpack: '^5.0.0', 35 | }, 36 | }, 37 | }, 38 | { 39 | name: 'ember-beta', 40 | npm: { 41 | dependencies: { 42 | 'ember-auto-import': '^2.0.0', 43 | }, 44 | devDependencies: { 45 | 'ember-source': await getChannelURL('beta'), 46 | webpack: '^5.0.0', 47 | }, 48 | }, 49 | }, 50 | { 51 | name: 'ember-canary', 52 | npm: { 53 | dependencies: { 54 | 'ember-auto-import': '^2.0.0', 55 | }, 56 | devDependencies: { 57 | 'ember-source': await getChannelURL('canary'), 58 | webpack: '^5.0.0', 59 | }, 60 | }, 61 | }, 62 | { 63 | name: 'ember-concurrency-1.x', 64 | npm: { 65 | dependencies: { 66 | 'ember-concurrency': '^1.3.0', 67 | }, 68 | }, 69 | }, 70 | { 71 | name: 'ember-concurrency-2.x', 72 | npm: { 73 | dependencies: { 74 | 'ember-concurrency': '^2.0.0', 75 | }, 76 | }, 77 | }, 78 | embroiderSafe(), 79 | embroiderOptimized(), 80 | ], 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /config/lint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baseRules = { 4 | // "Liberal Let": https://madhatted.com/2016/1/25/let-it-be 5 | // See, related: https://github.com/emberjs/eslint-plugin-ember-internal 6 | // "no-const-outside-module-scope" 7 | 'prefer-const': 'off', 8 | 9 | 'spaced-comment': [ 10 | 'error', 11 | 'always', 12 | { 13 | block: { 14 | balanced: true, 15 | exceptions: ['*', '/'], 16 | }, 17 | }, 18 | ], 19 | 20 | 'padding-line-between-statements': [ 21 | 'error', 22 | { blankLine: 'always', prev: '*', next: 'return' }, 23 | { blankLine: 'always', prev: '*', next: 'break' }, 24 | { blankLine: 'always', prev: '*', next: 'block-like' }, 25 | { blankLine: 'always', prev: 'block-like', next: '*' }, 26 | { blankLine: 'always', prev: ['const', 'let'], next: '*' }, 27 | { blankLine: 'always', prev: '*', next: ['const', 'let'] }, 28 | { blankLine: 'any', prev: ['const', 'let'], next: ['const', 'let'] }, 29 | ], 30 | 31 | 'prettier/prettier': [ 32 | 'error', 33 | { 34 | arrowParens: 'always', 35 | printWidth: 100, 36 | singleQuote: true, 37 | trailingComma: 'all', 38 | }, 39 | ], 40 | }; 41 | 42 | const parsers = { 43 | ts: { 44 | parser: '@typescript-eslint/parser', 45 | }, 46 | js: { 47 | parser: 'babel-eslint', 48 | parserOptions: { 49 | ecmaVersion: 2018, 50 | sourceType: 'module', 51 | ecmaFeatures: { 52 | legacyDecorators: true, 53 | }, 54 | }, 55 | }, 56 | }; 57 | 58 | const configs = { 59 | ts: [ 60 | 'eslint:recommended', 61 | 'plugin:ember/recommended', 62 | 'plugin:decorator-position/ember', 63 | 'plugin:@typescript-eslint/recommended', 64 | 'prettier', 65 | ], 66 | js: [ 67 | 'eslint:recommended', 68 | 'plugin:ember/recommended', 69 | 'plugin:prettier/recommended', 70 | 'plugin:decorator-position/ember', 71 | 'prettier', 72 | ], 73 | }; 74 | 75 | const plugins = { 76 | ts: ['ember', 'prettier', 'qunit', 'decorator-position'], 77 | js: ['ember', 'prettier', 'qunit', 'decorator-position'], 78 | }; 79 | 80 | const rules = { 81 | base: baseRules, 82 | ts: { 83 | // allow unused vars starting with _, 84 | // handy for destructuring and declaring all events 85 | // for greater context. 86 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 87 | 88 | // this rule doesn't work for module-based execution context 89 | '@typescript-eslint/no-use-before-define': 'off', 90 | 91 | // for consistency, whenever we declare an interface or type, 92 | // such as for Component args definitions. 93 | '@typescript-eslint/no-empty-interface': 'off', 94 | 95 | // member-accessibility is handled by native JS. 96 | // private fields / methods are stage 3 at the time of writing. 97 | '@typescript-eslint/explicit-member-accessibility': 'off', 98 | 99 | // prefer inference 100 | '@typescript-eslint/explicit-function-return-type': 'off', 101 | '@typescript-eslint/explicit-module-boundary-types': 'off', 102 | 103 | // better handled by prettier: 104 | '@typescript-eslint/indent': 'off', 105 | 106 | // much concise 107 | '@typescript-eslint/prefer-optional-chain': 'error', 108 | }, 109 | js: {}, 110 | }; 111 | 112 | module.exports = { parsers, configs, plugins, rules }; 113 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberAddon(defaults, { 7 | hinting: false, 8 | 9 | 'ember-cli-babel': { 10 | enableTypeScriptTransform: true, 11 | }, 12 | }); 13 | 14 | /* 15 | This build file specifies the options for the dummy test app of this 16 | addon, located in `/tests/dummy` 17 | This build file does *not* influence how the addon or the app using it 18 | behave. You most likely want to be modifying `./index.js` or app's build file 19 | */ 20 | 21 | const { maybeEmbroider } = require('@embroider/test-setup'); 22 | 23 | return maybeEmbroider(app, { 24 | packageRules: [ 25 | { 26 | // Components used during testing, 27 | // these are dynamically registered during the tests 28 | package: 'dummy', 29 | components: { 30 | '{{foo}}': { 31 | safeToIgnore: true, 32 | }, 33 | }, 34 | }, 35 | ], 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | 6 | // enable file-watching / live-reload 7 | // isDevelopingAddon: () => true, 8 | 9 | options: { 10 | 'ember-cli-babel': { 11 | enableTypeScriptTransform: true, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-resource-tasks", 3 | "version": "2.0.6", 4 | "description": "Resources for async functions in Ember", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember", 8 | "ember.js", 9 | "services", 10 | "Resource", 11 | "data", 12 | "async" 13 | ], 14 | "repository": "https://github.com/CrowdStrike/ember-resource-tasks", 15 | "license": "MIT", 16 | "author": "CrowdStrike UX Team", 17 | "directories": { 18 | "doc": "doc", 19 | "test": "tests" 20 | }, 21 | "scripts": { 22 | "build": "ember build --environment=production", 23 | "lint:docs": "remark .", 24 | "lint:docs-js": "eslint . --cache --ext md", 25 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel 'lint:!(fix)'", 26 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 27 | "lint:hbs": "ember-template-lint .", 28 | "lint:hbs:fix": "ember-template-lint . --fix", 29 | "lint:js": "eslint . --cache", 30 | "lint:js:fix": "eslint . --fix", 31 | "start": "ember serve", 32 | "test": "ember test", 33 | "prepack": "tsc --build", 34 | "postpublish": "tsc --build --clean", 35 | "postpack": "ember ts:clean" 36 | }, 37 | "publishConfig": { 38 | "registry": "https://registry.npmjs.org" 39 | }, 40 | "release": { 41 | "branches": [ 42 | "main", 43 | "master" 44 | ], 45 | "plugins": [ 46 | "@semantic-release/commit-analyzer", 47 | "@semantic-release/release-notes-generator", 48 | "@semantic-release/changelog", 49 | "@semantic-release/npm", 50 | "@semantic-release/github", 51 | "@semantic-release/git" 52 | ] 53 | }, 54 | "typesVersions": { 55 | "*": { 56 | "*": [ 57 | "declarations/*", 58 | "declarations/*/index" 59 | ] 60 | } 61 | }, 62 | "dependencies": { 63 | "@glimmer/tracking": "^1.0.4", 64 | "ember-cli-babel": "^7.26.11", 65 | "ember-cli-htmlbars": "^6.0.1", 66 | "ember-cli-typescript": "^5.0.0", 67 | "ember-could-get-used-to-this": "^1.0.1", 68 | "ember-test-waiters": "^2.1.3" 69 | }, 70 | "peerDependencies": { 71 | "ember-concurrency": "^2.2.0 || ^1.0.0", 72 | "ember-concurrency-decorators": "^2.0.0" 73 | }, 74 | "devDependencies": { 75 | "@commitlint/cli": "^11.0.0", 76 | "@commitlint/config-conventional": "^11.0.0", 77 | "@ember/optional-features": "^2.0.0", 78 | "@ember/test-helpers": "^2.6.0", 79 | "@embroider/test-setup": "^1.1.0", 80 | "@glimmer/component": "^1.0.4", 81 | "@semantic-release/changelog": "^5.0.1", 82 | "@semantic-release/git": "^9.0.1", 83 | "@types/ember": "3.16.5", 84 | "@types/ember-qunit": "^3.4.15", 85 | "@types/ember-resolver": "^5.0.10", 86 | "@types/ember__application": "^3.16.3", 87 | "@types/ember__array": "^3.16.4", 88 | "@types/ember__component": "^3.16.6", 89 | "@types/ember__controller": "^3.16.6", 90 | "@types/ember__debug": "^3.16.5", 91 | "@types/ember__destroyable": "^3.22.0", 92 | "@types/ember__engine": "^3.16.3", 93 | "@types/ember__error": "^3.16.1", 94 | "@types/ember__object": "^3.12.6", 95 | "@types/ember__polyfills": "^3.12.1", 96 | "@types/ember__routing": "^3.16.15", 97 | "@types/ember__runloop": "^3.16.3", 98 | "@types/ember__service": "^3.16.1", 99 | "@types/ember__string": "^3.16.3", 100 | "@types/ember__template": "^3.16.1", 101 | "@types/ember__test": "^3.16.1", 102 | "@types/ember__test-helpers": "^2.6.0", 103 | "@types/ember__utils": "^3.16.2", 104 | "@types/htmlbars-inline-precompile": "^1.0.1", 105 | "@types/qunit": "^2.11.2", 106 | "@types/rsvp": "^4.0.4", 107 | "@typescript-eslint/eslint-plugin": "4.15.0", 108 | "@typescript-eslint/parser": "^4.15.0", 109 | "babel-eslint": "^10.1.0", 110 | "broccoli-asset-rev": "^3.0.0", 111 | "ember-auto-import": "^1.12.1", 112 | "ember-cli": "~3.28.5", 113 | "ember-cli-dependency-checker": "^3.2.0", 114 | "ember-cli-inject-live-reload": "^2.1.0", 115 | "ember-cli-sri": "^2.1.1", 116 | "ember-cli-terser": "^4.0.2", 117 | "ember-concurrency": "^2.2.0", 118 | "ember-concurrency-decorators": "^2.0.3", 119 | "ember-concurrency-ts": "^0.3.1", 120 | "ember-disable-prototype-extensions": "^1.1.3", 121 | "ember-export-application-global": "^2.0.1", 122 | "ember-load-initializers": "^2.1.2", 123 | "ember-maybe-import-regenerator": "^0.1.6", 124 | "ember-page-title": "^7.0.0", 125 | "ember-qunit": "^5.1.5", 126 | "ember-resolver": "^8.0.3", 127 | "ember-source": "^3.28.8", 128 | "ember-source-channel-url": "^3.0.0", 129 | "ember-template-lint": "^3.2.0", 130 | "ember-try": "^2.0.0", 131 | "eslint": "^7.23.0", 132 | "eslint-config-prettier": "8.1.0", 133 | "eslint-plugin-decorator-position": "^2.2.18", 134 | "eslint-plugin-ember": "^10.3.0", 135 | "eslint-plugin-markdown": "^1.0.2", 136 | "eslint-plugin-node": "^11.1.0", 137 | "eslint-plugin-prettier": "3.3.1", 138 | "eslint-plugin-qunit": "^5.3.0", 139 | "loader.js": "^4.7.0", 140 | "npm-run-all": "^4.1.5", 141 | "prettier": "2.2.1", 142 | "qunit": "^2.17.2", 143 | "qunit-dom": "^2.0.0", 144 | "remark-cli": "^9.0.0", 145 | "remark-lint": "^8.0.0", 146 | "remark-preset-lint-recommended": "^5.0.0", 147 | "semantic-release": "^17.4.7", 148 | "typescript": "4.5.5" 149 | }, 150 | "remarkConfig": { 151 | "plugins": [ 152 | "remark-preset-lint-recommended" 153 | ] 154 | }, 155 | "engines": { 156 | "node": "12.* || >= 14" 157 | }, 158 | "ember": { 159 | "edition": "octane" 160 | }, 161 | "ember-addon": { 162 | "configPath": "tests/dummy/config" 163 | }, 164 | "volta": { 165 | "node": "16.13.2", 166 | "yarn": "1.22.17" 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /tests/dummy/app/app.ts: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import config from 'dummy/config/environment'; 4 | import loadInitializers from 'ember-load-initializers'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from 'my-app/config/environment' 6 | */ 7 | declare const config: { 8 | environment: string; 9 | modulePrefix: string; 10 | podModulePrefix: string; 11 | locationType: string; 12 | rootURL: string; 13 | APP: Record; 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.ts: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'dummy/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () { 10 | // Demo Routes 11 | }); 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title "ember-resource-tasks"}} 2 | 3 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/config/deprecation-workflow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | self.deprecationWorkflow = self.deprecationWorkflow || {}; 4 | self.deprecationWorkflow.config = { 5 | // For any new deprecations (not mentioned in the below workflow), 6 | // set to "true" to throw as a default so that the deprecation 7 | // - is dealt with right away / fixed 8 | // - added to the workflow below (along with a ticket / issue number). 9 | throwOnUnhandled: true, 10 | 11 | workflow: [ 12 | // Ember Deprecations 13 | // Test Deprecations 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.26.1", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--yarn" 15 | ] 16 | } 17 | ] 18 | }, 19 | { 20 | "name": "ember-addon-automated-ci", 21 | "version": "1.0.2", 22 | "blueprints": [ 23 | { 24 | "name": "ember-addon-automated-ci" 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from 'dummy/config/environment' 6 | * 7 | * For now these need to be managed by the developer 8 | * since different ember addons can materialize new entries. 9 | */ 10 | declare const config: { 11 | environment: string; 12 | modulePrefix: string; 13 | podModulePrefix: string; 14 | locationType: string; 15 | rootURL: string; 16 | host: string; 17 | 18 | APP: Record; 19 | }; 20 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false, 17 | }, 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | }, 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "jquery-integration": false, 4 | "template-only-glimmer-components": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = ['last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions']; 4 | 5 | module.exports = { 6 | browsers, 7 | }; 8 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/integration/resources/memoized-task-test.ts: -------------------------------------------------------------------------------- 1 | import { use } from 'ember-could-get-used-to-this'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { action } from '@ember/object'; 4 | import { module, test } from 'qunit'; 5 | import { setupTest } from 'ember-qunit'; 6 | import { setOwner } from '@ember/application'; 7 | import { settled } from '@ember/test-helpers'; 8 | import { waitFor } from '@ember/test-waiters'; 9 | import { destroy } from '@ember/destroyable'; 10 | 11 | import { MemoizedTask, valueFor } from 'ember-resource-tasks'; 12 | import { consumeTag } from 'ember-resource-tasks/-private/utils'; 13 | import { TASK_INSTANCE_FROM_CACHE } from 'ember-resource-tasks/-private/memoized-task'; 14 | 15 | import type ApplicationInstance from '@ember/application/instance'; 16 | 17 | module('Integration | Resource | MemoizedTask', function (hooks) { 18 | setupTest(hooks); 19 | 20 | let invocations = 0; 21 | 22 | hooks.beforeEach(function () { 23 | invocations = 0; 24 | }); 25 | 26 | class VanillaClass { 27 | @tracked left = 1; 28 | @tracked right = 0; 29 | 30 | @use 31 | data = valueFor( 32 | new MemoizedTask(() => { 33 | return { 34 | named: { 35 | cacheKey: 'data', 36 | args: [this.left, this.right], 37 | fn: this.add, 38 | }, 39 | }; 40 | }), 41 | ); 42 | 43 | @use 44 | alt = valueFor( 45 | new MemoizedTask(() => { 46 | return { 47 | named: { 48 | cacheKey: 'alt', 49 | args: [this.left, this.right], 50 | fn: this.add, 51 | }, 52 | }; 53 | }), 54 | ); 55 | 56 | @action 57 | @waitFor 58 | add(left: number, right: number) { 59 | let result = left + right; 60 | 61 | invocations++; 62 | 63 | return Promise.resolve(result); 64 | } 65 | } 66 | 67 | function create(owner: ApplicationInstance): VanillaClass { 68 | let instance = new VanillaClass(); 69 | 70 | setOwner(instance, owner); 71 | 72 | return instance; 73 | } 74 | 75 | test('it works', async function (assert) { 76 | let subject = create(this.owner); 77 | let subject2 = create(this.owner); 78 | 79 | assert.equal(invocations, 0); 80 | assert.equal(subject.data.value, undefined); 81 | 82 | await settled(); 83 | 84 | assert.equal(subject.data.value, 1); 85 | assert.equal(invocations, 1); 86 | 87 | subject.left = 2; 88 | consumeTag(subject, 'data'); 89 | await settled(); 90 | 91 | assert.equal(subject.data.value, 2); 92 | assert.equal(invocations, 2); 93 | 94 | subject2.left = subject.left; 95 | subject2.right = subject.right; 96 | 97 | consumeTag(subject2, 'data'); 98 | await settled(); 99 | 100 | assert.equal(subject2.data.value, 2, 'the cache bucket transfers to other instances'); 101 | assert.equal(invocations, 2, 'value used is from cache and not recomputed'); 102 | 103 | subject.right = 3; 104 | subject2.left = subject.left; 105 | subject2.right = subject.right; 106 | consumeTag(subject, 'data'); 107 | await settled(); 108 | 109 | assert.equal(subject.data.value, 5); 110 | assert.equal(invocations, 3); 111 | 112 | consumeTag(subject2, 'data'); 113 | await settled(); 114 | 115 | assert.equal(subject2.data.value, 5, 'the cache bucket transfers to other instances'); 116 | assert.equal(invocations, 3, 'value used is from cache and not recomputed'); 117 | }); 118 | 119 | test(`a canceled task in the cache is ignored`, async function (assert) { 120 | let subject = create(this.owner); 121 | 122 | assert.equal(invocations, 0); 123 | assert.equal(subject.data.value, undefined); 124 | 125 | let taskInstance = (subject.data as any)[TASK_INSTANCE_FROM_CACHE]; 126 | 127 | destroy(subject); 128 | 129 | await settled(); 130 | 131 | assert.equal(taskInstance.isCanceled, true, 'task is cancelled'); 132 | assert.equal(invocations, 1); 133 | 134 | let subject2 = create(this.owner); 135 | 136 | assert.equal(subject2.data.value, undefined); 137 | 138 | await settled(); 139 | 140 | taskInstance = (subject2.data as any)[TASK_INSTANCE_FROM_CACHE]; 141 | 142 | assert.equal(taskInstance.isCanceled, false, 'task is not cancelled'); 143 | 144 | assert.equal(subject2.data.value, 1); 145 | assert.equal(invocations, 2); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /tests/integration/resources/task-test.ts: -------------------------------------------------------------------------------- 1 | import { use } from 'ember-could-get-used-to-this'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import Component from '@glimmer/component'; 4 | import { setComponentTemplate } from '@ember/component'; 5 | import { hbs } from 'ember-cli-htmlbars'; 6 | import { action } from '@ember/object'; 7 | import { module, test } from 'qunit'; 8 | import { setupRenderingTest, setupTest } from 'ember-qunit'; 9 | import { setOwner } from '@ember/application'; 10 | import { settled, render } from '@ember/test-helpers'; 11 | import { waitFor } from '@ember/test-waiters'; 12 | 13 | import { Task, valueFor } from 'ember-resource-tasks'; 14 | import { consumeTag } from 'ember-resource-tasks/-private/utils'; 15 | 16 | import type ApplicationInstance from '@ember/application/instance'; 17 | 18 | module('Integration | Resource | Task', function () { 19 | module('in JS', function (hooks) { 20 | setupTest(hooks); 21 | 22 | class VanillaClass { 23 | @tracked left = 1; 24 | @tracked right = 0; 25 | 26 | @use 27 | data = valueFor( 28 | new Task(() => { 29 | return { named: { args: [this.left, this.right], fn: this.add } }; 30 | }), 31 | ); 32 | 33 | @action 34 | @waitFor 35 | add(left: number, right: number) { 36 | let result = left + right; 37 | 38 | return Promise.resolve(result); 39 | } 40 | } 41 | 42 | function create(owner: ApplicationInstance): VanillaClass { 43 | let instance = new VanillaClass(); 44 | 45 | setOwner(instance, owner); 46 | 47 | return instance; 48 | } 49 | 50 | test('it works', async function (assert) { 51 | let subject = create(this.owner); 52 | 53 | assert.equal(subject.data.value, undefined); 54 | 55 | await settled(); 56 | 57 | assert.equal(subject.data.value, 1); 58 | 59 | subject.left = 2; 60 | consumeTag(subject, 'data'); 61 | await settled(); 62 | 63 | assert.equal(subject.data.value, 2); 64 | 65 | subject.right = 3; 66 | consumeTag(subject, 'data'); 67 | await settled(); 68 | 69 | assert.equal(subject.data.value, 5); 70 | }); 71 | }); 72 | 73 | module('in Templates', function (hooks) { 74 | setupRenderingTest(hooks); 75 | 76 | test('it works', async function (assert) { 77 | class Foo extends Component<{ left: number; right: number }> { 78 | @use 79 | data = valueFor( 80 | new Task(() => { 81 | let { left, right } = this.args; 82 | 83 | return { named: { args: [left, right], fn: this.add } }; 84 | }), 85 | ); 86 | 87 | @action 88 | @waitFor 89 | add(left: number, right: number) { 90 | let result = left + right; 91 | 92 | return Promise.resolve(result); 93 | } 94 | } 95 | 96 | this.setProperties({ left: 1, right: 0 }); 97 | 98 | this.owner.register( 99 | 'component:foo', 100 | setComponentTemplate(hbs`The Result: {{this.data.value}}`, Foo), 101 | ); 102 | 103 | await render(hbs``); 104 | 105 | assert.dom().hasText('The Result: 1'); 106 | 107 | this.setProperties({ left: 2 }); 108 | await settled(); 109 | assert.dom().hasText('The Result: 2'); 110 | 111 | this.setProperties({ right: 3 }); 112 | await settled(); 113 | assert.dom().hasText('The Result: 5'); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /tests/test-helper.ts: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.compiler-options.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declarationDir": "dummy/declarations", 6 | "paths": { 7 | "dummy/tests/*": ["./*"], 8 | "dummy/*": ["./dummy/app/*", "./dummy/*"], 9 | "ember-resource-tasks": ["../declarations"], 10 | "ember-resource-tasks/*": ["../declarations/*"], 11 | "*": ["../types/*"] 12 | } 13 | }, 14 | "include": [".", "../types"], 15 | "references": [ 16 | { "path": "../addon" }, 17 | // { "path": "../addon-test-support" } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tests/unit/services/ember-source-tasks/-private/do-not-use/arg-cache-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | 4 | import type ArgCache from 'ember-resource-tasks/services/ember-resource-tasks/-private/do-not-use/arg-cache'; 5 | 6 | module('Unit | Service | ember-source-tasks/-private/do-not-use/arg-cache', function (hooks) { 7 | setupTest(hooks); 8 | 9 | test('it exists', function (assert) { 10 | let service = this.owner.lookup( 11 | 'service:ember-resource-tasks/-private/do-not-use/arg-cache', 12 | ) as ArgCache; 13 | 14 | assert.ok(service); 15 | assert.ok(service.getEnsuringBucket('this could be anything')); 16 | assert.ok(service.getEnsuringBucket('this could be anything') instanceof Map); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.compiler-options.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./config/build/tsconfig.compiler-options.ember.json", 3 | "compilerOptions": { 4 | "composite": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "composite": true 6 | }, 7 | "references": [ 8 | { "path": "addon" }, 9 | // { "path": "addon-test-support" }, 10 | { "path": "tests" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /types/dummy/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Types for compiled templates 2 | declare module 'ember-resource-tasks/templates/*' { 3 | import { TemplateFactory } from 'htmlbars-inline-precompile'; 4 | const tmpl: TemplateFactory; 5 | export default tmpl; 6 | } 7 | -------------------------------------------------------------------------------- /types/overrides.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import 'ember'; 3 | import '@ember/component'; 4 | import 'ember-concurrency-ts'; 5 | 6 | import './untyped-libraries'; 7 | 8 | 9 | import type { TemplateFactory } from 'ember-cli-htmlbars'; 10 | 11 | type TF = TemplateFactory; 12 | 13 | declare module '@ember/component' { 14 | // TODO: remove when this is actually a thing that exists? 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | export function setComponentTemplate(template: TF, klass: any): any; 17 | } 18 | -------------------------------------------------------------------------------- /types/untyped-libraries.d.ts: -------------------------------------------------------------------------------- 1 | type LazyTrackedArgs = { 2 | positional?: Array; 3 | named?: Record; 4 | }; 5 | 6 | declare module 'ember-could-get-used-to-this' { 7 | type ConstructorFn = (() => Args) | (() => Args['positional']); 8 | 9 | export const use: PropertyDecorator; 10 | export abstract class Resource { 11 | protected args: Args; 12 | 13 | // This is a lie, but makes the call site nice 14 | constructor(fn: ConstructorFn); 15 | 16 | abstract readonly value: unknown; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowdStrike/ember-resource-tasks/0444c12dcc31c6340306d004eb08bfaa202201e3/vendor/.gitkeep --------------------------------------------------------------------------------