├── .commitlintrc.js ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── ---bug-report.md │ ├── ---documentation.md │ └── ---feature-suggestion.md └── workflows │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .release-it.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENCE ├── README.md ├── babel.config.js ├── docs ├── .gitignore ├── docus.config.js ├── nuxt.config.js ├── package.json ├── pages │ └── en │ │ ├── 1.getting-started │ │ ├── 1.getting-started-nuxt.md │ │ └── 2.getting-started-vue.md │ │ ├── 1.index.md │ │ ├── 2.accessor │ │ ├── 1.accessor-introduction.md │ │ ├── 2.dynamic-modules.md │ │ └── 3.customisation.md │ │ ├── 3.store │ │ ├── 1.state.md │ │ ├── 2.getters.md │ │ ├── 3.mutations.md │ │ └── 4.actions.md │ │ └── 4.examples │ │ ├── 1.build.md │ │ ├── 2.runtime.md │ │ └── 3.vue.md └── static │ └── images │ ├── screenshot1.png │ └── screenshot2.png ├── examples ├── .gitignore ├── nuxt-ts │ ├── .editorconfig │ ├── components │ │ └── Logo.vue │ ├── index.d.ts │ ├── layouts │ │ └── default.vue │ ├── nuxt.config.ts │ ├── package.json │ ├── pages │ │ └── index.vue │ ├── static │ │ └── favicon.ico │ ├── store │ │ ├── index.ts │ │ └── submodule.ts │ └── tsconfig.json ├── nuxt │ ├── .editorconfig │ ├── components │ │ └── Logo.vue │ ├── index.d.ts │ ├── layouts │ │ └── default.vue │ ├── nuxt.config.js │ ├── package.json │ ├── pages │ │ ├── composition.vue │ │ └── index.vue │ ├── plugins │ │ └── composition-api.ts │ ├── prettier.config.js │ ├── static │ │ └── favicon.ico │ ├── store │ │ ├── index.ts │ │ └── submodule.ts │ └── tsconfig.json └── vue │ ├── .browserslistrc │ ├── .eslintrc.js │ ├── babel.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── HelloWorld.vue │ ├── index.d.ts │ ├── main.ts │ ├── shims-tsx.d.ts │ ├── shims-vue.d.ts │ └── store │ │ ├── index.ts │ │ └── submodule.ts │ └── tsconfig.json ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── nuxt-typed-vuex │ ├── .babelrc.js │ ├── jest.config.js │ ├── package.json │ ├── src │ │ └── index.ts │ ├── template │ │ └── plugin.js │ ├── test │ │ ├── fixture │ │ │ ├── index.d.ts │ │ │ ├── layouts │ │ │ │ └── default.vue │ │ │ ├── nuxt.config.js │ │ │ ├── pages │ │ │ │ └── index.vue │ │ │ ├── store │ │ │ │ ├── index.ts │ │ │ │ ├── nuxt.ts │ │ │ │ └── submodule.ts │ │ │ └── tsconfig.json │ │ ├── index.test.ts │ │ └── module.test.ts │ └── tsconfig.json └── typed-vuex │ ├── .babelrc.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── accessor.ts │ ├── index.ts │ ├── types │ │ ├── actions.ts │ │ ├── getters.ts │ │ ├── modules.ts │ │ ├── mutations.ts │ │ ├── state.ts │ │ ├── store.ts │ │ ├── utilities.ts │ │ └── utils.ts │ └── utils.ts │ ├── test │ ├── __snapshots__ │ │ ├── accessor.test.ts.snap │ │ └── utils.test.ts.snap │ ├── accessor.test.ts │ ├── fixture │ │ ├── index.ts │ │ └── submodule.ts │ ├── tsd │ │ ├── accessor.test-d.ts │ │ └── map-state.test-d.ts │ └── utils.test.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.js ├── renovate.json ├── tsconfig.json └── vercel.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/templates 2 | **/node_modules 3 | **/dist 4 | 5 | examples 6 | 7 | # Contains Lodash templates 8 | **/plugin.js 9 | 10 | **/lib 11 | **/test 12 | 13 | sw.js 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@typescript-eslint/recommended"], 3 | "plugins": ["@typescript-eslint"], 4 | "rules": { 5 | "@typescript-eslint/ban-types": "off", 6 | "@typescript-eslint/explicit-function-return-type": "off", 7 | "@typescript-eslint/member-delimiter-style": "off", 8 | "@typescript-eslint/no-explicit-any": "off" 9 | }, 10 | "parserOptions": { 11 | "parser": "@typescript-eslint/parser", 12 | "tsconfigRootDir": "./src", 13 | "ecmaVersion": 11, 14 | "sourceType": "module" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Something's not working 4 | title: 'fix: ' 5 | labels: bug 6 | assignees: danielroe 7 | 8 | --- 9 | 10 | **🐛 The bug** 11 | What isn't working? Describe what the bug is. 12 | 13 | **🛠️ To reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **🌈 Expected behaviour** 21 | What did you expect to happen? Is there a section in the docs about this? 22 | 23 | **ℹ️ Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4DA Documentation" 3 | about: How do I ... ? 4 | title: 'docs: ' 5 | labels: documentation 6 | assignees: danielroe 7 | 8 | --- 9 | 10 | **📚 Is your documentation request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I feel I should be able to [...] but I can't see how to do it from the docs. 12 | 13 | **🔍 Where should you find it?** 14 | What page of the docs do you expect this information to be found on? 15 | 16 | **ℹ️ Additional context** 17 | Add any other context or information. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F195 Feature suggestion" 3 | about: Suggest an idea 4 | title: 'feat: ' 5 | labels: enhancement 6 | assignees: danielroe 7 | 8 | --- 9 | 10 | **🆒 Your use case** 11 | Add a description of your use case, and how this feature would help you. 12 | > Ex. When I do [...] I would expect to be able to do [...] 13 | 14 | **🆕 The solution you'd like** 15 | Describe what you want to happen. 16 | 17 | **🔍 Alternatives you've considered** 18 | Have you considered any alternative solutions or features? 19 | 20 | **ℹ️ Additional info** 21 | Is there any other context you think would be helpful to know? 22 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "codeql" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '16 4 * * 4' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ 'javascript' ] 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v2 32 | with: 33 | languages: ${{ matrix.language }} 34 | # If you wish to specify custom queries, you can do so here or in a config file. 35 | # By default, queries listed here will override any specified in a config file. 36 | # Prefix the list here with "+" to use these queries and those in the config file. 37 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 38 | 39 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 40 | # If this step fails, then you should remove it and run the build manually (see below) 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v2 43 | 44 | # ℹ️ Command-line programs to run using the OS shell. 45 | # 📚 https://git.io/JvXDl 46 | 47 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 48 | # and modify them (or add more) to build your code if your project 49 | # uses a compiled language 50 | 51 | #- run: | 52 | # make bootstrap 53 | # make release 54 | 55 | - name: Perform CodeQL Analysis 56 | uses: github/codeql-action/analyze@v2 57 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - renovate/* 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | latest: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - run: npm i -g --force corepack && corepack enable 20 | 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 20 24 | cache: "pnpm" 25 | 26 | - name: Install dependencies 27 | run: pnpm install --frozen-lockfile 28 | 29 | - name: Build 30 | run: pnpm build 31 | 32 | - name: Test 33 | run: pnpm test 34 | 35 | - name: Coverage 36 | uses: codecov/codecov-action@v3 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node template 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # Nuxt generate 71 | dist 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless 78 | 79 | # IDE 80 | .idea 81 | 82 | # Service worker 83 | sw.* 84 | 85 | packages/*/dist/* 86 | !packages/*/dist/.gitkeep 87 | .now 88 | 89 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 90 | .yarn/* 91 | !.yarn/releases 92 | !.yarn/plugins 93 | !.yarn/sdks 94 | !.yarn/versions 95 | .pnp.* 96 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release v${version}" 4 | }, 5 | "github": { 6 | "release": true, 7 | "releaseName": "v${version}" 8 | }, 9 | "plugins": { 10 | "@release-it/conventional-changelog": { 11 | "preset": "conventionalcommits", 12 | "infile": "CHANGELOG.md" 13 | }, 14 | "release-it-yarn-workspaces": true 15 | }, 16 | "npm": false 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [0.3.1](https://github.com/danielroe/typed-vuex/compare/0.3.0...0.3.1) (2022-06-08) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * add `namespaced` and `strict` properties to store interfaces ([#272](https://github.com/danielroe/typed-vuex/issues/272)) ([bb5ff8d](https://github.com/danielroe/typed-vuex/commit/bb5ff8d373bd8d7ef7c543bddf338dd6ecb8ff28)) 9 | * update `vuex` peer dependency and bump dev-dependencies ([#308](https://github.com/danielroe/typed-vuex/issues/308)) ([2ee9967](https://github.com/danielroe/typed-vuex/commit/2ee996709f2758730707bf34d49a5aa3e8de5a16)) 10 | 11 | ## [0.3.0](https://github.com/danielroe/typed-vuex/compare/0.2.0...0.3.0) (2022-02-19) 12 | 13 | 14 | ### Features 15 | 16 | * add `createMapper` convenience utility ([#264](https://github.com/danielroe/typed-vuex/issues/264)) ([a26d2da](https://github.com/danielroe/typed-vuex/commit/a26d2da67c51beb0733e44c1da05482eaeca1d90)) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * check Function instance for store state rather than typeof ([4363a56](https://github.com/danielroe/typed-vuex/commit/4363a5602d1031944d226117f04970785f14b5c8)) 22 | 23 | ## [0.2.0](https://github.com/danielroe/typed-vuex/compare/0.1.22...0.2.0) (2021-04-29) 24 | 25 | 26 | ### ⚠ BREAKING CHANGES 27 | 28 | * all imports are now from `typed-vuex` rather than `nuxt-typed-vuex`, which is *exclusively* the module in your `nuxt.config` 29 | 30 | **Migration path**: Search/replace through your project `nuxt-typed-vuex` for `typed-vuex`, with the sole exception of your `nuxt.config.js`. 😊 31 | 32 | ### Bug Fixes 33 | 34 | * expose all typed-vuex types ([#231](https://github.com/danielroe/typed-vuex/issues/231)) ([9d6c479](https://github.com/danielroe/typed-vuex/commit/9d6c479b0d0781916596fd27e5f5e5e32f8579c9)) 35 | 36 | 37 | ### Code Refactoring 38 | 39 | * separate module from runtime ([#219](https://github.com/danielroe/typed-vuex/issues/219)) ([b8d556b](https://github.com/danielroe/typed-vuex/commit/b8d556b041e162c66de3e8dbd11c8f1dd5461c2a)) 40 | 41 | ### [0.1.22](https://github.com/danielroe/nuxt-typed-vuex/compare/0.1.21...0.1.22) (2020-09-30) 42 | 43 | 44 | ### Performance Improvements 45 | 46 | * increase tree-shakeability of module ([8b98e33](https://github.com/danielroe/nuxt-typed-vuex/commit/8b98e330c560c70468e536592773f2f2ada5555f)) 47 | 48 | ### [0.1.21](https://github.com/danielroe/nuxt-typed-vuex/compare/0.1.20...0.1.21) (2020-08-30) 49 | 50 | 51 | ### Features 52 | 53 | * **nuxt-typed-vuex:** automatically transpile module ([35eb0a0](https://github.com/danielroe/nuxt-typed-vuex/commit/35eb0a0f221718e9f29e1f100a574f7c1e88bf1d)) 54 | 55 | ### [0.1.20](https://github.com/danielroe/nuxt-typed-vuex/compare/nuxt-typed-vuex@0.1.19...0.1.20) (2020-08-02) 56 | 57 | ### Bug Fixes 58 | 59 | - move @nuxt/types and vuex to peerDeps ([4d6029e](https://github.com/danielroe/nuxt-typed-vuex/commit/4d6029ea582cc055010612b8427b4ae12fcd4fac)) 60 | - state lazy evaluation in accessor ([#75](https://github.com/danielroe/nuxt-typed-vuex/issues/75)) ([bae68f5](https://github.com/danielroe/nuxt-typed-vuex/commit/bae68f59dfd7f05511469569943a56a15583b9b9)) 61 | 62 | ## [0.1.19](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.18...typed-vuex@0.1.19) (2020-06-13) 63 | 64 | **Note:** Version bump only 65 | 66 | ## [0.1.18](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.17...typed-vuex@0.1.18) (2020-04-20) 67 | 68 | **Note:** Version bump only 69 | 70 | ## [0.1.17](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.16...typed-vuex@0.1.17) (2020-03-05) 71 | 72 | ### Bug Fixes 73 | 74 | - move @nuxt/types and vuex to peerDeps ([4d6029e](https://github.com/danielroe/nuxt-typed-vuex/commit/4d6029ea582cc055010612b8427b4ae12fcd4fac)) 75 | 76 | ## [0.1.16](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.15...typed-vuex@0.1.16) (2020-02-08) 77 | 78 | ### Bug Fixes 79 | 80 | - state lazy evaluation in accessor ([#75](https://github.com/danielroe/nuxt-typed-vuex/issues/75)) ([bae68f5](https://github.com/danielroe/nuxt-typed-vuex/commit/bae68f59dfd7f05511469569943a56a15583b9b9)) 81 | 82 | ## [0.1.15](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.14...typed-vuex@0.1.15) (2019-12-12) 83 | 84 | **Note:** Version bump only 85 | 86 | ## 0.1.14 (2019-12-12) 87 | 88 | **Note:** Version bump only 89 | 90 | ## [0.1.14-alpha.2](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.14-alpha.0...typed-vuex@0.1.14-alpha.2) (2019-12-12) 91 | 92 | ### Performance Improvements 93 | 94 | - exclude source maps ([e638b97](https://github.com/danielroe/nuxt-typed-vuex/commit/e638b977d971636f59cd58886fe69a0d008175b3)) 95 | 96 | ## [0.1.14-alpha.1](https://github.com/danielroe/nuxt-typed-vuex/compare/typed-vuex@0.1.14-alpha.0...typed-vuex@0.1.14-alpha.1) (2019-12-12) 97 | 98 | ### Performance Improvements 99 | 100 | - exclude source maps ([e638b97](https://github.com/danielroe/nuxt-typed-vuex/commit/e638b977d971636f59cd58886fe69a0d008175b3)) 101 | 102 | ## 0.1.14-alpha.0 (2019-12-08) 103 | 104 | **Note:** Version bump only for package typed-vuex 105 | 106 | ### [0.1.13](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.13-beta.0...v0.1.13) (2019-11-26) 107 | 108 | ### [0.1.12](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.11...v0.1.12) (2019-11-22) 109 | 110 | ### Bug Fixes 111 | 112 | - allow any kind of dispatch ([75f4637](https://github.com/danielroe/nuxt-typed-vuex/commit/75f463723d54949a98100c21481e5bae5f6d7a87)) 113 | 114 | ### [0.1.11](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.10...v0.1.11) (2019-11-22) 115 | 116 | ### Bug Fixes 117 | 118 | - restore dispatch to action context ([019f1bb](https://github.com/danielroe/nuxt-typed-vuex/commit/019f1bb53ddac38fcde81c195a4df3c9afc49f57)), closes [#43](https://github.com/danielroe/nuxt-typed-vuex/issues/43) 119 | 120 | ### [0.1.10](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.10-beta.2...v0.1.10) (2019-11-06) 121 | 122 | ### [0.1.9](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.8...v0.1.9) (2019-11-03) 123 | 124 | ### Bug Fixes 125 | 126 | - require @nuxt/types and vuex as peer deps ([c1ebc33](https://github.com/danielroe/nuxt-typed-vuex/commit/c1ebc33)) 127 | 128 | ### Features 129 | 130 | - add example codesandbox ([a6b89b1](https://github.com/danielroe/nuxt-typed-vuex/commit/a6b89b1)) 131 | - allow simpler usage of useAccessor ([9cb8cd2](https://github.com/danielroe/nuxt-typed-vuex/commit/9cb8cd2)), closes [#13](https://github.com/danielroe/nuxt-typed-vuex/issues/13) 132 | 133 | ### [0.1.8](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.7...v0.1.8) (2019-10-13) 134 | 135 | ### [0.1.7](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.6...v0.1.7) (2019-10-13) 136 | 137 | ### Bug Fixes 138 | 139 | - use correct paths on Windows machines ([56b714a](https://github.com/danielroe/nuxt-typed-vuex/commit/56b714a)) 140 | 141 | ### [0.1.6](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.5...v0.1.6) (2019-10-11) 142 | 143 | ### Features 144 | 145 | - add rootState and rootGetters type helpers ([2661017](https://github.com/danielroe/nuxt-typed-vuex/commit/2661017)) 146 | 147 | ### [0.1.5](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.4...v0.1.5) (2019-10-06) 148 | 149 | ### Bug Fixes 150 | 151 | - allow defining rootState & rootGetters ([fe484b2](https://github.com/danielroe/nuxt-typed-vuex/commit/fe484b2)) 152 | 153 | ### Features 154 | 155 | - add helper functions for use within store ([59bed72](https://github.com/danielroe/nuxt-typed-vuex/commit/59bed72)) 156 | 157 | ### [0.1.4](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.3...v0.1.4) (2019-10-05) 158 | 159 | ### Bug Fixes 160 | 161 | - state is still required ([91ae3d7](https://github.com/danielroe/nuxt-typed-vuex/commit/91ae3d7)) 162 | 163 | ### Features 164 | 165 | - support state objects ([97ed828](https://github.com/danielroe/nuxt-typed-vuex/commit/97ed828)) 166 | 167 | ### [0.1.3](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.2...v0.1.3) (2019-10-05) 168 | 169 | ### Bug Fixes 170 | 171 | - allow partial submodules ([d27bea0](https://github.com/danielroe/nuxt-typed-vuex/commit/d27bea0)) 172 | - allow specifying different rootState ([a837a24](https://github.com/danielroe/nuxt-typed-vuex/commit/a837a24)) 173 | - convert plugin to ts ([ae240eb](https://github.com/danielroe/nuxt-typed-vuex/commit/ae240eb)) 174 | 175 | ### [0.1.2](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.1...v0.1.2) (2019-10-04) 176 | 177 | ### Features 178 | 179 | - add commit and dispatch options ([ef320e7](https://github.com/danielroe/nuxt-typed-vuex/commit/ef320e7)) 180 | 181 | ### 0.1.1 (2019-10-04) 182 | 183 | ### Features 184 | 185 | - initial commit ([b3eed9a](https://github.com/danielroe/nuxt-typed-vuex/commit/b3eed9a)) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at daniel@danielcroe.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Daniel Roe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🏦 Typed Vuex

2 |

A strongly-typed store accessor for vanilla Vuex

3 | 4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 |

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |

29 | 30 |

31 | Read documentation 32 |

33 | 34 | ## Summary 35 | 36 | This module provides a store accessor and helper type methods so you can access your vanilla Vuex store in a strongly typed way. 37 | 38 | ![Image showing autocomplete on this.$accessor](./docs/static/images/screenshot1.png) 39 | 40 | ![Image showing autocomplete on commit within store](./docs/static/images/screenshot2.png) 41 | 42 | **Note**: This has been developed to suit my needs but additional use cases and contributions are very welcome. 43 | 44 | [MIT License](./LICENSE) - Copyright © Daniel Roe 45 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | babelrcRoots: ['packages/*'], 4 | } 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 2 | .yarn/* 3 | !.yarn/releases 4 | !.yarn/plugins 5 | !.yarn/sdks 6 | !.yarn/versions 7 | .pnp.* 8 | -------------------------------------------------------------------------------- /docs/docus.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Typed Vuex', 3 | url: 'https://typed-vuex.roe.dev/', 4 | twitter: 'danielcroe', 5 | github: { 6 | repo: 'danielroe/typed-vuex', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /docs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { withDocus } from 'docus' 2 | 3 | export default withDocus({ 4 | rootDir: __dirname, 5 | image: { 6 | provider: 'static', 7 | }, 8 | }) 9 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "nuxt dev", 5 | "now-build": "nuxt generate" 6 | }, 7 | "devDependencies": { 8 | "docus": "0.5.6", 9 | "nuxt-i18n": "6.28.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/1.getting-started-nuxt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting started (Nuxt) 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | ## Install module 7 | 8 | 1. Install Nuxt module: 9 | 10 | :::::code-group 11 | ::::code-block{label="Yarn" active} 12 | 13 | ```bash 14 | yarn add nuxt-typed-vuex 15 | ``` 16 | 17 | :::: 18 | ::::code-block{label="NPM"} 19 | 20 | ```bash 21 | npm install nuxt-typed-vuex --save 22 | ``` 23 | 24 | :::: 25 | ::::: 26 | 27 | :::alert{type="info"} 28 | This will also install `typed-vuex` in your project, which is where the store accessor lives. All the helper functions are imported from `typed-vuex`. 29 | ::: 30 | 31 | 2. Add module to your `nuxt.config`: 32 | 33 | ```ts 34 | buildModules: [ 35 | 'nuxt-typed-vuex', 36 | ], 37 | ``` 38 | 39 | :::alert{type="info"} 40 | `buildModules` require Nuxt 2.10+. If you are using an older version, add `nuxt-typed-vuex` to `modules` instead. 41 | ::: 42 | 43 | ## Add type definitions 44 | 45 | The module will inject a store accessor throughout your project (`$accessor`). It is not typed by default, so you will need to add types. 46 | 47 | ### Defining the accessor type 48 | 49 | In your root store module, add the following code: 50 | 51 | ```ts{}[store/index.ts] 52 | import { getAccessorType } from 'typed-vuex' 53 | 54 | // Import all your submodules 55 | import * as submodule from '~/store/submodule' 56 | 57 | // Keep your existing vanilla Vuex code for state, getters, mutations, actions, plugins, etc. 58 | // ... 59 | 60 | // This compiles to nothing and only serves to return the correct type of the accessor 61 | export const accessorType = getAccessorType({ 62 | state, 63 | getters, 64 | mutations, 65 | actions, 66 | modules: { 67 | // The key (submodule) needs to match the Nuxt namespace (e.g. ~/store/submodule.ts) 68 | submodule, 69 | }, 70 | }) 71 | ``` 72 | 73 | :::alert{type="info"} 74 | This may look different if you split your modules into separate files for `state`, `actions`, `mutations` and `getters`. 75 | ::: 76 | 77 | ### Creating type definitions 78 | 79 | Add the following type definitions to your project: 80 | 81 | ```ts{}[index.d.ts] 82 | import { accessorType } from '~/store' 83 | 84 | declare module 'vue/types/vue' { 85 | interface Vue { 86 | $accessor: typeof accessorType 87 | } 88 | } 89 | 90 | declare module '@nuxt/types' { 91 | interface NuxtAppOptions { 92 | $accessor: typeof accessorType 93 | } 94 | 95 | interface Context { 96 | $accessor: typeof accessorType, 97 | } 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/pages/en/1.getting-started/2.getting-started-vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting started (Vue) 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | If you would like to benefit from a typed accessor to your store, but you're not using Nuxt, you can still use `typed-vuex`. 7 | 8 | :::alert 9 | Many of this project's default settings are based on Nuxt, so please file an issue if you experience any problems. 10 | ::: 11 | 12 | ## Setup 13 | 14 | 1. Install package: 15 | 16 | :::::code-group 17 | ::::code-block{label="Yarn" active} 18 | 19 | ```bash 20 | yarn add typed-vuex 21 | ``` 22 | 23 | :::: 24 | ::::code-block{label="NPM"} 25 | 26 | ```bash 27 | npm install typed-vuex --save 28 | ``` 29 | 30 | :::: 31 | ::::: 32 | 33 | 2. Instantiate your accessor 34 | 35 | ```ts{}[src/store/index.ts] 36 | import Vue from 'vue' 37 | import Vuex from 'vuex' 38 | 39 | import { 40 | useAccessor, 41 | getterTree, 42 | mutationTree, 43 | actionTree, 44 | } from 'typed-vuex' 45 | 46 | Vue.use(Vuex) 47 | 48 | const state = () => ({ 49 | email: '', 50 | }) 51 | 52 | const getters = getterTree(state, { 53 | email: state => state.email, 54 | fullEmail: state => state.email, 55 | }) 56 | 57 | const mutations = mutationTree(state, { 58 | setEmail(state, newValue: string) { 59 | state.email = newValue 60 | }, 61 | 62 | initialiseStore() { 63 | console.log('initialised') 64 | }, 65 | }) 66 | 67 | const actions = actionTree( 68 | { state, getters, mutations }, 69 | { 70 | async resetEmail({ commit }) { 71 | commit('setEmail', 'a@a.com') 72 | }, 73 | } 74 | ) 75 | 76 | const storePattern = { 77 | state, 78 | mutations, 79 | actions, 80 | } 81 | 82 | const store = new Vuex.Store(storePattern) 83 | 84 | export const accessor = useAccessor(store, storePattern) 85 | 86 | // Optionally, inject accessor globally 87 | Vue.prototype.$accessor = accessor 88 | 89 | export default store 90 | ``` 91 | 92 | 3. Define types. 93 | 94 | If you've injected the accessor globally, you'll want to define its type: 95 | 96 | ```ts{}[index.d.ts] 97 | import Vue from 'vue' 98 | import { accessor } from './src/store' 99 | 100 | declare module 'vue/types/vue' { 101 | interface Vue { 102 | $accessor: typeof accessor 103 | } 104 | } 105 | ``` 106 | 107 | ## Usage within a component 108 | 109 | ```ts 110 | import { Component, Vue } from 'vue-property-decorator' 111 | import { accessor } from '../store' 112 | 113 | @Component 114 | export default class SampleComponent extends Vue { 115 | get email() { 116 | // This (behind the scenes) returns getters['email'] 117 | return accessor.email 118 | 119 | // Or, with a globally injected accessor 120 | return this.$accessor.email 121 | } 122 | 123 | resetEmail() { 124 | // Executes dispatch('submodule/resetEmail', 'new@email.com') 125 | accessor.submodule.resetEmail('new@email.com') 126 | 127 | // Or, with a globally injected accessor 128 | this.$accessor.submodule.resetEmail('new@email.com') 129 | } 130 | } 131 | ``` 132 | 133 | ## Usage within the store 134 | 135 | You can use the accessor within the store or a store module. 136 | 137 | ```ts 138 | import { actionTree } from 'typed-vuex' 139 | import { accessor } from '.' 140 | 141 | const actions = actionTree( 142 | { state, getters, mutations }, 143 | { 144 | async resetEmail({ commit }) { 145 | accessor.submodule.initialise() 146 | commit('setEmail', 'a@a.com') 147 | }, 148 | } 149 | ) 150 | ``` 151 | -------------------------------------------------------------------------------- /docs/pages/en/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | Nuxt Typed Vuex is made up of two packages: 7 | 8 | 1. `typed-vuex` - a typed store accessor with helper functions, with no Nuxt dependencies 9 | 2. `nuxt-typed-vuex` - a Nuxt module that auto-injects this accessor throughout your project 10 | 11 | ## Why another package? 12 | 13 | Typing vanilla Vuex is complicated. Many people choose a class-based approach with Typescript decorators, but this can cause issues. Although Vuex provides limited type definitions for the store itself, it's complicated to access it in a type-safe way. 14 | 15 | `nuxt-typed-vuex` was developed to address this problem. It features: 16 | 17 | :::list 18 | 19 | - store definition with vanilla Vuex code 20 | - strongly typed accessor 21 | - fast performance 22 | - small footprint 23 | - compatible with Nuxt 24 | - access this.\$axios and the app/store instance from within actions 25 | - minimal setup/boilerplate 26 | 27 | ::: 28 | 29 | ![Image showing autocomplete on this.$accessor](/images/screenshot1.png) 30 | 31 | ![Image showing autocomplete on commit within store](/images/screenshot2.png) 32 | 33 | ## Alternatives 34 | 35 | If you would prefer a class-based approach, good options include [`vuex-module-decorators`](https://github.com/championswimmer/vuex-module-decorators) and [`vuex-class-component`](https://github.com/michaelolof/vuex-class-component). 36 | -------------------------------------------------------------------------------- /docs/pages/en/2.accessor/1.accessor-introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | The accessor serves two purposes: 7 | 8 | - It wraps the store so that it can be typed without conflicting with the default types for `$store` in a Nuxt project. 9 | - It allows us to avoid creating impossible type definitions for namespaced magic strings, like `commit('mysubmodule/mutation')`. 10 | 11 | ## Structure 12 | 13 | 1. Getters, state, mutations and actions are flattened. 14 | 2. Getters take priority over state (so state is not included if a getter of the same name exists). 15 | 3. Modules are namespaced. 16 | 17 | :::alert{type="warning"} 18 | Because the accessor is flattened, you should avoid using the same name more than once between your getters, state, mutations and actions or you may receive the following error: `Cannot set property of # which has only a getter.` 19 | ::: 20 | 21 | So, for example: 22 | 23 | ```ts 24 | // Accesses this.$store.state.email 25 | this.$accessor.email 26 | 27 | // Accesses this.$store.getters['fullName'] 28 | this.$accessor.fullName 29 | 30 | // Runs this.$store.dispatch('initialiseStore') 31 | this.$accessor.initialiseStore() 32 | 33 | // Runs this.$store.commit('SET_NAME', 'John Doe') 34 | this.$accessor.SET_NAME('John Doe') 35 | 36 | // Accesses this.$store.state.submodule.id 37 | this.$accessor.submodule.id 38 | 39 | // etc. 40 | ``` 41 | 42 | ## Typing the accessor 43 | 44 | Adding types is simple. A helper function, `getAccessorType`, is provided, which compiles to nothing and only serves to return the correct type of the accessor so that it can be used where you see fit. 45 | 46 | Make sure you define types correctly following [these instructions](/getting-started/getting-started-nuxt#add-type-definitions). 47 | 48 | ## Using the accessor 49 | 50 | ### Components, `fetch` and `asyncData` 51 | 52 | ```ts{}[components/sampleComponent.vue] 53 | import Vue from 'vue' 54 | 55 | export default Vue.extend({ 56 | fetch({ app: { $accessor } }) { 57 | $accessor.fetchItems() 58 | }, 59 | asyncData({ app: { $accessor } }) { 60 | return { 61 | myEmail: $accessor.email, 62 | } 63 | }, 64 | computed: { 65 | email() { 66 | // This (behind the scenes) returns this.$store.getters['email'] 67 | return this.$accessor.email 68 | }, 69 | }, 70 | methods: { 71 | resetEmail() { 72 | // Executes this.$store.dispatch('submodule/resetEmail', 'new@email.com') 73 | this.$accessor.submodule.resetEmail('new@email.com') 74 | }, 75 | }, 76 | }) 77 | ``` 78 | 79 | ### Composition API 80 | 81 | ```ts{}[components/sampleComponent.vue] 82 | import { defineComponent, computed } from '@nuxtjs/composition-api' 83 | 84 | export default defineComponent({ 85 | setup(_props, { root }) { 86 | const counter = computed(() => root.$accessor.counter) 87 | 88 | return { 89 | counter, 90 | } 91 | }, 92 | }) 93 | ``` 94 | 95 | Since you are already using Composition API, you might as well create a hook dedicated to this 96 | 97 | ```ts{}[hooks/useAccessor.ts] 98 | import { wrapProperty } from '@nuxtjs/composition-api' 99 | 100 | export const useAccessor = wrapProperty('$accessor', false) 101 | ``` 102 | 103 | Should your IDE not properly recognize type declarations setup as described in [these instructions](/getting-started/getting-started-nuxt#add-type-definitions), 104 | you may want to explicitly define accessor types like in the following example: 105 | 106 | ```ts{}[hooks/useAccessor.ts] 107 | import { wrapProperty } from '@nuxtjs/composition-api' 108 | import { accessorType } from '~/store' 109 | 110 | export const useAccessor = (): typeof accessorType => 111 | wrapProperty('$accessor', false)() 112 | ``` 113 | 114 | ### Middleware 115 | 116 | ```ts{}[middleware/test.ts] 117 | import { Context } from '@nuxt/types' 118 | 119 | export default ({ redirect, app: { $accessor } }: Context) => { 120 | // You can access the store here 121 | if ($accessor.email) return redirect('/') 122 | } 123 | ``` 124 | 125 | ## Mapping properties into your component 126 | 127 | `typed-vuex` also exports a convenience helper function to allow you to easily map the accessor into your component. 128 | 129 | ```ts{}[components/sampleComponent.vue] 130 | import Vue from 'vue' 131 | 132 | // Nuxt 133 | import { accessorType } from '~/store' 134 | const mapper = createMapper(accessorType) 135 | // Vue 136 | import { accessor } from './src/store' 137 | const mapper = createMapper(accessor) 138 | 139 | export default Vue.extend({ 140 | computed: { 141 | // Direct mapping to a property 142 | ...mapper('name'), 143 | // or array of properties 144 | ...mapper(['name', 'email']), 145 | // or submodules 146 | ...mapper('submodule', ['name', 'email']), 147 | }, 148 | methods: { 149 | // All your methods should be included in the methods object, 150 | // whether they are mutations, actions (or even a method returned 151 | // by a getter or stored in state) 152 | ...mapper('resetName'), 153 | resetEmail() { 154 | // You can access directly off your component instance 155 | console.log(this.name, this.email) 156 | this.resetName() 157 | }, 158 | }, 159 | }) 160 | ``` 161 | -------------------------------------------------------------------------------- /docs/pages/en/2.accessor/2.dynamic-modules.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dynamic modules 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | You can also use `typed-vuex` with dynamic modules. 7 | 8 | ## Sample module 9 | 10 | ```ts{}[modules/dynamic-module.ts] 11 | export const namespaced = true 12 | 13 | export const state = () => ({ 14 | emails: [] as string[], 15 | }) 16 | 17 | export const mutations = mutationTree(state, { 18 | addEmail(state, newEmail: string) { 19 | state.emails.push(newEmail) 20 | }, 21 | }) 22 | ``` 23 | 24 | ## Accessing the module 25 | 26 | You might want to use the store 27 | 28 | ```ts{}[components/my-component.vue] 29 | import Vue from 'vue 30 | 31 | import { useAccessor, getAccessorType } from 'typed-vuex' 32 | import dynamicModule from '~/modules/dynamic-module' 33 | 34 | const accessorType = getAccessorType(dynamicModule) 35 | 36 | export default Vue.extend({ 37 | data: () => ({ 38 | accessor: null as typeof accessorType | null, 39 | }), 40 | beforeCreated() { 41 | // make sure the namespaces match! 42 | this.$store.registerModule('dynamicModule', dynamicModule, { 43 | preserveState: false, 44 | }) 45 | const accessor = useAccessor(this.$store, dynamicModule, 'dynamicModule') 46 | 47 | // this works and is typed 48 | accessor.addEmail('my@email.com') 49 | 50 | // but you might want to save the accessor for use elsewhere in your component 51 | this.accessor = accessor 52 | }, 53 | methods: { 54 | anotherMethod() { 55 | // ... such as here 56 | if (this.accessor) { 57 | this.accessor.addEmail('my@email.com') 58 | } 59 | } 60 | } 61 | }) 62 | 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/pages/en/2.accessor/3.customisation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Customisation 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | You might choose to customise the accessor function using the helper function `useAccessor`. You pass this an object with `state`, `actions`, `mutations`, etc. Child modules are within a `modules` property. 7 | 8 | ## Injecting a custom accessor 9 | 10 | ```ts{}[plugins/custom-store-accessor.ts] 11 | import { useAccessor } from 'typed-vuex' 12 | import { InjectKey } from 'vue/types/options' 13 | import { Context } from '@nuxt/types' 14 | 15 | import * as store from '~/store' 16 | import * as submodule from '~/store/submodule' 17 | 18 | type Inject = (name: InjectKey, property: unknown) => void 19 | 20 | export default async ({ store }: Context, inject: Inject) => { 21 | inject( 22 | 'accessor', 23 | useAccessor({ 24 | ...store, 25 | modules: { 26 | submodule, 27 | }, 28 | }) 29 | ) 30 | } 31 | ``` 32 | 33 | :::alert 34 | If you are using a custom accessor in a Nuxt project, bear in mind that `useAccessor` used on its own will treat modules as non-namespaced unless they include `namespaced: true`. 35 | ::: 36 | 37 | ## Typing your custom accessor 38 | 39 | You can use the helper function `getAccessorType` to access the type of the accessor you've generated - by passing it the exact same object that `useAccessor` receives. 40 | 41 | It gets compiled down to `() => {}` so there is no performance hit. 42 | 43 | ```ts 44 | import { getAccessorType } from 'typed-vuex' 45 | 46 | import * as store from '~/store' 47 | import * as submodule from '~/store/submodule' 48 | 49 | const accessorType = getAccessorType({ 50 | ...store, 51 | modules: { 52 | submodule, 53 | }, 54 | }) 55 | 56 | // Now you can access the type of the accessor. 57 | const accessor: typeof accessorType 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/pages/en/3.store/1.state.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: State 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | In order to use the accessor, you should define your state with a function that returns the initial state of the store. 7 | 8 | If there is any ambiguity in your initial object (of particular note are empty arrays), make sure to provide types as well. 9 | 10 | ```ts 11 | export const state = () => ({ 12 | // AVOID: This results in emails being typed as never[] 13 | emails: [], 14 | 15 | // This is correct 16 | emails: [] as string[], 17 | }) 18 | 19 | // If needed, you can define state for use in vanilla Vuex types 20 | export type RootState = ReturnType 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/pages/en/3.store/2.getters.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getters 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | A getter is a function that receives `state`, `getters`, `rootState` and `rootGetters`. 7 | 8 | This package provides a helper function to reduce boilerplate: `getterTree`. This function adds typings and returns the getters passed to it, without transforming them. 9 | 10 | :::alert 11 | `typed-vuex` does not currently type-check anything but the state received. 12 | ::: 13 | 14 | :::::code-group 15 | ::::code-block{label="Helper function" active} 16 | ```ts 17 | import { getterTree } from 'typed-vuex' 18 | 19 | export const getters = getterTree(state, { 20 | // Type-checked 21 | email: state => (state.emails.length ? state.emails[0] : ''), 22 | // NOT type-checked 23 | aDependentGetter: (_state, getters) => getters.email, 24 | }) 25 | ``` 26 | :::: 27 | ::::code-block{label="Vanilla"} 28 | 29 | ```ts 30 | export const getters = { 31 | // Type-checked 32 | email: (state: RootState) => (state.emails.length ? state.emails[0] : ''), 33 | // NOT type-checked 34 | aDependentGetter: (_state: RootState, getters: any) => getters.email, 35 | } 36 | ``` 37 | :::: 38 | ::::: 39 | 40 | :::alert{type="info"} 41 | Even if you do not use the `getterTree` helper function, make sure not to use the `GetterTree` type provided by Vuex. This will interfere with type inference. You won't lose out by omitting it, as Typescript will complain if you pass an improperly formed getter into [the `getAccessorType` function](/getting-started/getting-started-nuxt#add-type-definitions). 42 | ::: 43 | -------------------------------------------------------------------------------- /docs/pages/en/3.store/3.mutations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mutations 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | Mutations are functions that receive store state and an optional payload. 7 | 8 | This package provides a helper function to reduce boilerplate: `mutationTree`. This function adds typings and returns the mutations passed to it, without transforming them. 9 | 10 | :::::code-group 11 | ::::code-block{label="Helper function" active} 12 | 13 | ```ts 14 | import { mutationTree } from 'typed-vuex' 15 | 16 | export const mutations = mutationTree(state, { 17 | setEmail(state, newValue: string) { 18 | state.email = newValue 19 | }, 20 | 21 | initialiseStore() { 22 | console.log('initialised') 23 | }, 24 | }) 25 | ``` 26 | 27 | :::: 28 | ::::code-block{label="Vanilla"} 29 | 30 | ```ts 31 | export const mutations = { 32 | setEmail(state: RootState, newValue: string) { 33 | state.email = newValue 34 | }, 35 | 36 | initialiseStore() { 37 | console.log('initialised') 38 | }, 39 | } 40 | ``` 41 | 42 | :::: 43 | ::::: 44 | 45 | :::alert{type="info"} 46 | 47 | 1. Even if you do not use the `mutationTree` helper function, make sure not to use the `MutationTree` type provided by Vuex. This will interfere with type inference. You won't lose out by omitting it, as Typescript will complain if you pass an improperly formed mutation into [the `getAccessorType` function](/getting-started/getting-started-nuxt#add-type-definitions). 48 | 49 | 2. This package does not support [object-style commits](https://vuex.vuejs.org/guide/mutations.html#object-style-commit). 50 | 51 | ::: 52 | -------------------------------------------------------------------------------- /docs/pages/en/3.store/4.actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Actions 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | --- 5 | 6 | Actions are async functions that have access to the Vuex instance and are passed a context object and optional payload. 7 | 8 | This package provides a helper function to reduce boilerplate: `actionTree`. This function adds typings and returns the actions passed to it, without transforming them. 9 | 10 | :::alert 11 | If you use the helper function, only the `commit` method is typed - and only for mutations within the module. 12 | ::: 13 | 14 | :::alert{type="info"} 15 | 16 | 1. Even if you do not use the `actionTree` helper function, make sure not to use the `ActionTree` type provided by Vuex. This will interfere with type inference. You won't lose out by omitting it, as Typescript will complain if you pass an incompatible action into [the `getAccessorType` function](/getting-started/getting-started-nuxt#add-type-definitions). 17 | 18 | 2. This package does not support [object-style dispatches](https://vuex.vuejs.org/guide/actions.html). 19 | 20 | ::: 21 | 22 | ## Referencing other modules 23 | 24 | If you need to reference other modules, or dispatch actions within the module, it should be done in a type-safe way through `this.app.$accessor`. You will need to add a return type to your action to avoid TypeScript complaining about self-referential type definitions. For example: 25 | 26 | ```ts 27 | export const actions = actionTree( 28 | { state, getters, mutations }, 29 | { 30 | async resetEmail(): Promise { 31 | this.app.$accessor.anotherModule.doSomething() 32 | }, 33 | } 34 | ) 35 | ``` 36 | 37 | Should your IDE not properly recognize type declarations setup as described in [these instructions](/getting-started/getting-started-nuxt#add-type-definitions), 38 | you may want to explicitly define accessor types like in the following examples: 39 | 40 | ```ts 41 | export const actions = actionTree( 42 | { state, getters, mutations }, 43 | { 44 | async resetEmail(): Promise { 45 | // accessor as an explicitly typed constant 46 | const accessor: typeof accessorType = this.app.$accessor 47 | accessor.anotherModule.doSomething() 48 | 49 | // inline type definition 50 | (this.app.$accessor as typeof accessorType).anotherModule.doSomething() 51 | }, 52 | } 53 | ) 54 | ``` 55 | 56 | ## Example 57 | 58 | :::::code-group 59 | ::::code-block{label="Helper function" active} 60 | 61 | ```ts 62 | import { actionTree } from 'typed-vuex' 63 | import { Context } from '@nuxt/types' 64 | 65 | export const actions = actionTree( 66 | { state, getters, mutations }, 67 | { 68 | async resetEmail({ commit, dispatch, getters, state }) { 69 | // Typed 70 | commit('initialiseStore') 71 | let a = getters.email 72 | let b = state._email 73 | 74 | // Not typed 75 | dispatch('resetEmail') 76 | 77 | // Typed 78 | this.app.$accessor.resetEmail() 79 | }, 80 | async nuxtServerInit(_vuexContext, nuxtContext: Context) { 81 | console.log(nuxtContext.req) 82 | }, 83 | } 84 | ) 85 | ``` 86 | :::: 87 | ::::code-block{label="Vanilla"} 88 | ```ts 89 | import { Context } from '@nuxt/types' 90 | 91 | export const actions = { 92 | async resetEmail( 93 | this: Store, 94 | { state, commit }: ActionContext, 95 | payload: string 96 | ) { 97 | // Typed 98 | state.email 99 | 100 | // Not typed - avoid 101 | commit('initialiseStore') 102 | 103 | // Typed 104 | this.app.$accessor.initialiseStore() 105 | }, 106 | async nuxtServerInit( 107 | _vuexContext: ActionContext, 108 | nuxtContext: Context 109 | ) { 110 | console.log(nuxtContext.req) 111 | }, 112 | } 113 | ``` 114 | :::: 115 | ::::: 116 | -------------------------------------------------------------------------------- /docs/pages/en/4.examples/1.build.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Normal usage 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | fullscreen: true 5 | --- 6 | 7 | :::code-sandbox{src="https://codesandbox.io/s/github/danielroe/typed-vuex/tree/main/examples/nuxt?from-embed"} 8 | -------------------------------------------------------------------------------- /docs/pages/en/4.examples/2.runtime.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Typescript runtime 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | fullscreen: true 5 | --- 6 | 7 | :::code-sandbox{src="https://codesandbox.io/s/github/danielroe/typed-vuex/tree/main/examples/nuxt-ts?from-embed"} 8 | -------------------------------------------------------------------------------- /docs/pages/en/4.examples/3.vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Without Nuxt 3 | description: 'Vanilla, strongly-typed store accessor.' 4 | fullscreen: true 5 | --- 6 | 7 | :::code-sandbox{src="https://codesandbox.io/s/github/danielroe/typed-vuex/tree/main/examples/vue?from-embed"} 8 | -------------------------------------------------------------------------------- /docs/static/images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/docs/static/images/screenshot1.png -------------------------------------------------------------------------------- /docs/static/images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/docs/static/images/screenshot2.png -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # Mac OSX 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | 92 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 93 | */.yarn/* 94 | !*/.yarn/releases 95 | !*/.yarn/plugins 96 | !*/.yarn/sdks 97 | !*/.yarn/versions 98 | -------------------------------------------------------------------------------- /examples/nuxt-ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/nuxt-ts/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /examples/nuxt-ts/index.d.ts: -------------------------------------------------------------------------------- 1 | import { accessorType } from '~/store' 2 | 3 | declare module 'vue/types/vue' { 4 | interface Vue { 5 | $accessor: typeof accessorType 6 | } 7 | } 8 | 9 | declare module '@nuxt/types' { 10 | interface NuxtAppOptions { 11 | $accessor: typeof accessorType 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/nuxt-ts/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 56 | -------------------------------------------------------------------------------- /examples/nuxt-ts/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from '@nuxt/types' 2 | 3 | const config: Configuration = { 4 | /* 5 | ** Nuxt.js dev-modules 6 | */ 7 | buildModules: ['@nuxt/typescript-build', 'nuxt-typed-vuex'], 8 | } 9 | 10 | export default config 11 | -------------------------------------------------------------------------------- /examples/nuxt-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-typed-vuex-ts-example", 3 | "version": "1.0.0", 4 | "description": "Example project for nuxt-typed-vuex.", 5 | "author": "Daniel Roe", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate", 12 | "now-build": "nuxt generate" 13 | }, 14 | "dependencies": { 15 | "nuxt": "latest", 16 | "vue-class-component": "^7.2.6" 17 | }, 18 | "devDependencies": { 19 | "@nuxt/types": "latest", 20 | "@nuxt/typescript-build": "latest", 21 | "nuxt-typed-vuex": "latest", 22 | "vue-property-decorator": "^9.1.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/nuxt-ts/pages/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 48 | 49 | 81 | -------------------------------------------------------------------------------- /examples/nuxt-ts/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/examples/nuxt-ts/static/favicon.ico -------------------------------------------------------------------------------- /examples/nuxt-ts/store/index.ts: -------------------------------------------------------------------------------- 1 | import { getAccessorType, mutationTree, actionTree } from 'typed-vuex' 2 | 3 | import * as submodule from './submodule' 4 | 5 | export const state = () => ({ 6 | email: '', 7 | }) 8 | 9 | type RootState = ReturnType 10 | 11 | export const getters = { 12 | email: (state: RootState) => state.email, 13 | fullEmail: (state: RootState) => state.email, 14 | } 15 | 16 | export const mutations = mutationTree(state, { 17 | setEmail(state, newValue: string) { 18 | state.email = newValue 19 | }, 20 | 21 | initialiseStore() { 22 | console.log('initialised') 23 | }, 24 | }) 25 | 26 | export const actions = actionTree( 27 | { state, getters, mutations }, 28 | { 29 | async resetEmail({ commit }) { 30 | commit('setEmail', 'a@a.com') 31 | }, 32 | } 33 | ) 34 | 35 | export const accessorType = getAccessorType({ 36 | actions, 37 | getters, 38 | mutations, 39 | state, 40 | modules: { 41 | submodule, 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /examples/nuxt-ts/store/submodule.ts: -------------------------------------------------------------------------------- 1 | import { getterTree, mutationTree, actionTree } from 'typed-vuex' 2 | 3 | export const state = () => ({ 4 | firstName: '', 5 | lastName: '', 6 | }) 7 | 8 | export const getters = getterTree(state, { 9 | fullName: state => state.firstName + ' ' + state.lastName, 10 | }) 11 | 12 | export const mutations = mutationTree(state, { 13 | setFirstName(state, newValue: string) { 14 | state.firstName = newValue 15 | }, 16 | setLastName(state, newValue: string) { 17 | state.lastName = newValue 18 | }, 19 | }) 20 | 21 | export const actions = actionTree( 22 | { state, getters, mutations }, 23 | { 24 | initialise({ commit }) { 25 | commit('setFirstName', 'John') 26 | commit('setLastName', 'Baker') 27 | }, 28 | setName({ commit }, newName: string) { 29 | const names = newName.split(' ') 30 | commit('setFirstName', names[0]) 31 | if (names.length > 1) commit('setLastName', names[1]) 32 | }, 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /examples/nuxt-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "esnext.asynciterable", 9 | "dom" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./*" 23 | ] 24 | }, 25 | "types": [ 26 | "@nuxt/types", 27 | "@types/node" 28 | ] 29 | }, 30 | "exclude": [ 31 | "node_modules" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/nuxt/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/nuxt/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /examples/nuxt/index.d.ts: -------------------------------------------------------------------------------- 1 | import { accessorType } from '~/store' 2 | 3 | declare module 'vue/types/vue' { 4 | interface Vue { 5 | $accessor: typeof accessorType 6 | } 7 | } 8 | 9 | declare module '@nuxt/types' { 10 | interface NuxtAppOptions { 11 | $accessor: typeof accessorType 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/nuxt/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 56 | -------------------------------------------------------------------------------- /examples/nuxt/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /* 3 | ** Plugins to load before mounting the App 4 | */ 5 | plugins: ['~/plugins/composition-api.ts'], 6 | /* 7 | ** Nuxt.js dev-modules 8 | */ 9 | buildModules: ['@nuxt/typescript-build', 'nuxt-typed-vuex'], 10 | } 11 | -------------------------------------------------------------------------------- /examples/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-typed-vuex-example", 3 | "version": "1.0.0", 4 | "description": "Example project for nuxt-typed-vuex.", 5 | "author": "Daniel Roe", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate", 12 | "now-build": "nuxt generate" 13 | }, 14 | "dependencies": { 15 | "@vue/composition-api": "latest", 16 | "nuxt": "latest" 17 | }, 18 | "devDependencies": { 19 | "@nuxt/types": "latest", 20 | "@nuxt/typescript-build": "latest", 21 | "nuxt-typed-vuex": "latest", 22 | "vue-property-decorator": "^9.1.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/nuxt/pages/composition.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | 29 | -------------------------------------------------------------------------------- /examples/nuxt/pages/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | 44 | 76 | -------------------------------------------------------------------------------- /examples/nuxt/plugins/composition-api.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueCompositionApi from "@vue/composition-api"; 3 | 4 | Vue.use(VueCompositionApi); 5 | -------------------------------------------------------------------------------- /examples/nuxt/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | } 6 | -------------------------------------------------------------------------------- /examples/nuxt/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/examples/nuxt/static/favicon.ico -------------------------------------------------------------------------------- /examples/nuxt/store/index.ts: -------------------------------------------------------------------------------- 1 | import { getAccessorType, mutationTree, actionTree } from 'typed-vuex' 2 | import { Context } from '@nuxt/types' 3 | 4 | import * as submodule from './submodule' 5 | 6 | export const state = () => ({ 7 | email: '', 8 | }) 9 | 10 | type RootState = ReturnType 11 | 12 | export const getters = { 13 | email: (state: RootState) => state.email, 14 | fullEmail: (state: RootState) => state.email, 15 | } 16 | 17 | export const mutations = mutationTree(state, { 18 | setEmail(state, newValue: string) { 19 | state.email = newValue 20 | }, 21 | 22 | initialiseStore() { 23 | console.log('initialised') 24 | }, 25 | }) 26 | 27 | export const actions = actionTree( 28 | { state, getters, mutations }, 29 | { 30 | async resetEmail({ commit }) { 31 | commit('setEmail', 'a@a.com') 32 | }, 33 | 34 | async nuxtServerInit(_vuexContext, nuxtContext: Context) { 35 | console.log(nuxtContext.req) 36 | }, 37 | } 38 | ) 39 | 40 | export const accessorType = getAccessorType({ 41 | actions, 42 | getters, 43 | mutations, 44 | state, 45 | modules: { 46 | submodule, 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /examples/nuxt/store/submodule.ts: -------------------------------------------------------------------------------- 1 | import { getterTree, mutationTree, actionTree } from 'typed-vuex' 2 | 3 | export const state = () => ({ 4 | firstName: '', 5 | lastName: '', 6 | }) 7 | 8 | export const getters = getterTree(state, { 9 | fullName: state => state.firstName + ' ' + state.lastName, 10 | }) 11 | 12 | export const mutations = mutationTree(state, { 13 | setFirstName(state, newValue: string) { 14 | state.firstName = newValue 15 | }, 16 | setLastName(state, newValue: string) { 17 | state.lastName = newValue 18 | }, 19 | }) 20 | 21 | export const actions = actionTree( 22 | { state, getters, mutations }, 23 | { 24 | initialise({ commit }) { 25 | commit('setFirstName', 'John') 26 | commit('setLastName', 'Baker') 27 | }, 28 | setName({ commit }, newName: string) { 29 | const names = newName.split(' ') 30 | commit('setFirstName', names[0]) 31 | if (names.length > 1) commit('setLastName', names[1]) 32 | }, 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /examples/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext", 8 | "esnext.asynciterable", 9 | "dom" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noEmit": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": [ 19 | "./*" 20 | ], 21 | "@/*": [ 22 | "./*" 23 | ] 24 | }, 25 | "types": [ 26 | "@nuxt/types", 27 | "@types/node" 28 | ] 29 | }, 30 | "exclude": [ 31 | "node_modules" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /examples/vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | '@vue/typescript' 10 | ], 11 | rules: { 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 14 | }, 15 | parserOptions: { 16 | parser: '@typescript-eslint/parser' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-typed-vuex-vue-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "latest", 12 | "typed-vuex": "latest", 13 | "vue": "^2.7.16", 14 | "vue-class-component": "^7.2.6", 15 | "vue-property-decorator": "^9.1.2", 16 | "vuex": "^3.6.2" 17 | }, 18 | "devDependencies": { 19 | "@typescript-eslint/eslint-plugin": "^4.33.0", 20 | "@typescript-eslint/parser": "^4.33.0", 21 | "@vue/cli-plugin-babel": "^4.5.19", 22 | "@vue/cli-plugin-eslint": "^4.5.19", 23 | "@vue/cli-plugin-typescript": "^4.5.19", 24 | "@vue/cli-plugin-vuex": "^4.5.19", 25 | "@vue/cli-service": "^4.5.19", 26 | "@vue/eslint-config-typescript": "^5.1.0", 27 | "eslint": "^7.32.0", 28 | "eslint-plugin-vue": "^6.2.2", 29 | "typescript": "^4.9.5", 30 | "vue-template-compiler": "^2.7.16" 31 | }, 32 | "resolutions": { 33 | "acorn": ">=7.4.1", 34 | "minimist": ">=1.2.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/examples/vue/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vuex-test2 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /examples/vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielroe/typed-vuex/1a99a78757b38b58e0dda0a106bb2030884d4440/examples/vue/src/assets/logo.png -------------------------------------------------------------------------------- /examples/vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /examples/vue/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { accessor } from './store' 3 | 4 | declare module 'vue/types/vue' { 5 | interface Vue { 6 | $accessor: typeof accessor 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import store from './store' 4 | 5 | Vue.config.productionTip = false 6 | 7 | new Vue({ 8 | store, 9 | render: h => h(App) 10 | }).$mount('#app') 11 | -------------------------------------------------------------------------------- /examples/vue/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/vue/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { useAccessor, mutationTree, actionTree } from 'typed-vuex' 4 | 5 | import * as submodule from './submodule' 6 | 7 | export const state = () => ({ 8 | email: '', 9 | }) 10 | 11 | type RootState = ReturnType 12 | 13 | export const getters = { 14 | email: (state: RootState) => state.email, 15 | fullEmail: (state: RootState) => state.email, 16 | } 17 | 18 | export const mutations = mutationTree(state, { 19 | setEmail(state, newValue: string) { 20 | state.email = newValue 21 | }, 22 | 23 | initialiseStore() { 24 | // console.log('initialised') 25 | }, 26 | }) 27 | 28 | export const actions = actionTree( 29 | { state, getters, mutations }, 30 | { 31 | async resetEmail({ commit }) { 32 | commit('setEmail', 'a@a.com') 33 | }, 34 | } 35 | ) 36 | 37 | export const storePattern = { 38 | state, 39 | mutations, 40 | actions, 41 | modules: { submodule }, 42 | } 43 | 44 | Vue.use(Vuex) 45 | 46 | const store = new Vuex.Store(storePattern) 47 | export const accessor = useAccessor(store, storePattern) 48 | 49 | Vue.prototype.$accessor = accessor 50 | 51 | export default store 52 | -------------------------------------------------------------------------------- /examples/vue/src/store/submodule.ts: -------------------------------------------------------------------------------- 1 | import { getterTree, mutationTree, actionTree } from 'typed-vuex' 2 | 3 | import { accessor } from '.' 4 | 5 | export const namespaced = true 6 | 7 | export const state = () => ({ 8 | firstName: '', 9 | lastName: '', 10 | }) 11 | 12 | export const getters = getterTree(state, { 13 | fullName: state => state.firstName + ' ' + state.lastName, 14 | }) 15 | 16 | export const mutations = mutationTree(state, { 17 | setFirstName(state, newValue: string) { 18 | state.firstName = newValue 19 | }, 20 | setLastName(state, newValue: string) { 21 | state.lastName = newValue 22 | }, 23 | }) 24 | 25 | export const actions = actionTree( 26 | { state, getters, mutations }, 27 | { 28 | initialise({ commit }) { 29 | commit('setFirstName', accessor.email) 30 | commit('setLastName', 'Baker') 31 | }, 32 | setName({ commit }, newName: string) { 33 | const names = newName.split(' ') 34 | commit('setFirstName', names[0]) 35 | if (names.length > 1) commit('setLastName', names[1]) 36 | }, 37 | } 38 | ) 39 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: ['/packages/*/jest.config.js'], 4 | transform: { 5 | '\\.(js|ts)$': [ 6 | 'babel-jest', 7 | { 8 | presets: ['@babel/preset-env', '@babel/preset-typescript'], 9 | plugins: ['@babel/plugin-transform-runtime'], 10 | }, 11 | ], 12 | }, 13 | testEnvironment: 'node', 14 | collectCoverage: true, 15 | coverageThreshold: { 16 | global: { 17 | branches: 100, 18 | functions: 100, 19 | lines: 100, 20 | statements: 100, 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "exact": true, 6 | "packages": ["packages/*"], 7 | "conventionalCommits": true, 8 | "command": { 9 | "version": { 10 | "conventionalCommits": true 11 | }, 12 | "release": { 13 | "conventionalCommits": true 14 | }, 15 | "publish": { 16 | "npmClient": "npm", 17 | "conventionalCommits": true, 18 | "message": "chore(release): publish [skip-ci]" 19 | }, 20 | "init": { 21 | "exact": true 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.3.1", 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "scripts": { 8 | "build": "pnpm -r build", 9 | "lint": "eslint --ext .ts,.tsx,.js .", 10 | "lint:fix": "npm run lint -- --fix", 11 | "prepare": "simple-git-hooks", 12 | "test": "pnpm run lint && jest && pnpm run -r test:types" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "7.25.8", 16 | "@babel/plugin-transform-runtime": "^7.25.7", 17 | "@babel/preset-env": "^7.25.8", 18 | "@babel/preset-typescript": "^7.25.7", 19 | "@commitlint/cli": "^17.8.1", 20 | "@commitlint/config-conventional": "^17.8.1", 21 | "@nuxtjs/eslint-config-typescript": "10.0.0", 22 | "@types/jest": "28.1.8", 23 | "@typescript-eslint/eslint-plugin": "5.62.0", 24 | "@typescript-eslint/parser": "5.62.0", 25 | "conventional-changelog-conventionalcommits": "^5.0.0", 26 | "eslint": "8.57.1", 27 | "jest": "28.1.3", 28 | "jest-environment-jsdom": "^28.1.3", 29 | "semver": "^7.6.3", 30 | "simple-git-hooks": "^2.11.1", 31 | "typescript": "4.9.5", 32 | "unbuild": "^0.7.6" 33 | }, 34 | "resolutions": { 35 | "vue3": "npm:vue@latest" 36 | }, 37 | "simple-git-hooks": { 38 | "commit-msg": "npx commitlint --edit", 39 | "pre-commit": "pnpm lint:fix" 40 | }, 41 | "packageManager": "pnpm@9.12.2" 42 | } 43 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-typescript'], 3 | plugins: ['@babel/plugin-transform-runtime'] 4 | } 5 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['test'], 3 | } 4 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-typed-vuex", 3 | "version": "0.3.1", 4 | "description": "A typed store accessor for Nuxt.", 5 | "keywords": [ 6 | "vuex", 7 | "nuxt", 8 | "typescript", 9 | "vue", 10 | "vuejs", 11 | "nuxtjs", 12 | "nuxt-module" 13 | ], 14 | "repository": "danielroe/typed-vuex", 15 | "license": "MIT", 16 | "author": { 17 | "name": "Daniel Roe", 18 | "email": "daniel@roe.dev", 19 | "url": "https://github.com/danielroe" 20 | }, 21 | "main": "dist/index.cjs", 22 | "module": "dist/index.mjs", 23 | "types": "dist/index.d.ts", 24 | "files": [ 25 | "dist/**/*", 26 | "template/*", 27 | "!**/*.map" 28 | ], 29 | "scripts": { 30 | "build": "unbuild", 31 | "dev": "nuxt test/fixture" 32 | }, 33 | "dependencies": { 34 | "typed-vuex": "0.3.1", 35 | "upath": "^2.0.1" 36 | }, 37 | "devDependencies": { 38 | "@babel/preset-env": "~7.25.8", 39 | "@nuxt/test-utils": "^0.2.2", 40 | "@nuxt/types": "^2.18.1", 41 | "@nuxt/typescript-build": "^2.1.0", 42 | "core-js": "3.38.1", 43 | "nuxt": "^2.18.1", 44 | "ts-loader": "^8.4.0", 45 | "tsd": "^0.20.0" 46 | }, 47 | "peerDependencies": { 48 | "@nuxt/types": "^2.15.8" 49 | }, 50 | "tsd": { 51 | "directory": "test/tsd", 52 | "compilerOptions": { 53 | "rootDir": "." 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/src/index.ts: -------------------------------------------------------------------------------- 1 | import { join, resolve } from 'upath' 2 | 3 | import type { Module } from '@nuxt/types' 4 | 5 | import { name, version } from '../package.json' 6 | 7 | /** 8 | * @private 9 | */ 10 | const nuxtTypedVuex: Module = function nuxtTypedVuex() { 11 | if (!this.options.store) console.warn('You do not have a store defined.') 12 | 13 | this.addPlugin({ 14 | src: resolve(__dirname, '../template/plugin.js'), 15 | fileName: 'nuxt-typed-vuex.js', 16 | options: { 17 | store: join(this.options.buildDir, 'store'), 18 | }, 19 | }) 20 | 21 | this.options.build.transpile = /* istanbul ignore next */ this.options.build.transpile || [] 22 | this.options.build.transpile.push(/typed-vuex/) 23 | } 24 | 25 | ;(nuxtTypedVuex as any).meta = { name, version } 26 | 27 | export default nuxtTypedVuex 28 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/template/plugin.js: -------------------------------------------------------------------------------- 1 | import { getAccessorFromStore } from 'typed-vuex' 2 | 3 | import { createStore } from '<%= options.store %>' 4 | 5 | const storeAccessor = getAccessorFromStore(createStore()) 6 | 7 | export default async ({ store }, inject) => { 8 | inject('accessor', storeAccessor(store)) 9 | } 10 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/index.d.ts: -------------------------------------------------------------------------------- 1 | import { accessorType } from '~/store' 2 | 3 | declare module 'vue/types/vue' { 4 | interface Vue { 5 | $accessor: typeof accessorType 6 | } 7 | } 8 | 9 | declare module '@nuxt/types' { 10 | interface NuxtAppOptions { 11 | $accessor: typeof accessorType 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | buildModules: ['@nuxt/typescript-build', '../../src'], 3 | build: { 4 | babel: { 5 | presets(_ctx, [_preset, options]) { 6 | options.corejs = 3 7 | }, 8 | }, 9 | }, 10 | typescript: { 11 | typeCheck: false, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/pages/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 47 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/store/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vuex' 2 | import { 3 | getStoreType, 4 | getAccessorType, 5 | mutationTree, 6 | actionTree, 7 | createMapper, 8 | } from '../../../../typed-vuex/src' 9 | 10 | import * as submodule from './submodule' 11 | 12 | export const state = () => ({ 13 | email: '', 14 | }) 15 | 16 | type RootState = ReturnType 17 | 18 | export const getters = { 19 | email: (state: RootState) => state.email, 20 | fullEmail: (state: RootState) => state.email, 21 | } 22 | 23 | export const mutations = mutationTree(state, { 24 | setEmail(state, newValue: string) { 25 | state.email = newValue 26 | }, 27 | 28 | initialiseStore() { 29 | console.log('initialised') 30 | }, 31 | }) 32 | 33 | export const actions = actionTree( 34 | { state, getters, mutations }, 35 | { 36 | async resetEmail({ commit }) { 37 | commit('setEmail', 'a@a.com') 38 | }, 39 | async resetEmailWithOptionalPayload({ commit }, optionalPayload?: string) { 40 | commit('setEmail', optionalPayload || 'a@a.com') 41 | }, 42 | } 43 | ) 44 | 45 | export const plugins: Plugin[] = [] 46 | 47 | export const pattern = { 48 | actions, 49 | getters, 50 | mutations, 51 | state, 52 | modules: { 53 | submodule, 54 | }, 55 | } 56 | 57 | export const storeType = getStoreType(pattern) 58 | 59 | export const accessorType = getAccessorType(pattern) 60 | export const mapper = createMapper(accessorType) 61 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/store/nuxt.ts: -------------------------------------------------------------------------------- 1 | import { actionTree } from '../../../../typed-vuex/src' 2 | 3 | export const state = () => ({ 4 | n: '', 5 | }) 6 | 7 | export const actions = actionTree( 8 | { state }, 9 | { 10 | testNuxtTyping() { 11 | this.app.$accessor.initialiseStore() 12 | }, 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/store/submodule.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getterTree, 3 | mutationTree, 4 | actionTree, 5 | useAccessor, 6 | } from '../../../../typed-vuex/src' 7 | import { pattern } from '.' 8 | 9 | export const state = () => ({ 10 | firstName: '', 11 | lastName: '', 12 | }) 13 | 14 | export const getters = getterTree(state, { 15 | fullName: state => state.firstName + ' ' + state.lastName, 16 | }) 17 | 18 | export const mutations = mutationTree(state, { 19 | setFirstName(state, newValue: string) { 20 | state.firstName = newValue 21 | }, 22 | setLastName(state, newValue: string) { 23 | state.lastName = newValue 24 | }, 25 | }) 26 | 27 | export const actions = actionTree( 28 | { state, getters, mutations }, 29 | { 30 | initialise({ commit }) { 31 | const accessor = useAccessor(this, pattern) 32 | commit('setFirstName', 'John' + accessor.email.split('@')[0]) 33 | commit('setLastName', 'Baker') 34 | }, 35 | setName({ commit }, newName: string) { 36 | const names = newName.split(' ') 37 | commit('setFirstName', names[0]) 38 | if (names.length > 1) commit('setLastName', names[1]) 39 | }, 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/fixture/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "lib": ["esnext", "esnext.asynciterable", "dom"], 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "noEmit": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "baseUrl": ".", 14 | "types": ["node", "@nuxt/types", "webpack-env"], 15 | "esModuleInterop": true, 16 | "paths": { 17 | "~/*": ["./*"] 18 | }, 19 | "allowJs": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { join, relative, resolve } from 'path' 2 | import { readdirSync, statSync } from 'fs' 3 | 4 | import { setupTest, getNuxt } from '@nuxt/test-utils' 5 | 6 | const { Nuxt } = require('nuxt') 7 | 8 | function walkDir(dir: string, callback: (filename: string) => any) { 9 | readdirSync(dir).forEach(f => { 10 | const dirPath = join(dir, f) 11 | const isDirectory = statSync(dirPath).isDirectory() 12 | isDirectory ? walkDir(dirPath, callback) : callback(join(dir, f)) 13 | }) 14 | } 15 | 16 | function iterateOnDirectory( 17 | directory: string, 18 | callback: (filename: string) => any 19 | ) { 20 | walkDir(resolve(__dirname, directory), filename => callback(filename)) 21 | } 22 | 23 | describe('nuxt-typed-vuex build', () => { 24 | setupTest({ 25 | testDir: __dirname, 26 | build: true, 27 | generate: true, 28 | }) 29 | 30 | test('build files (.nuxt) includes plugin', async () => { 31 | const { options } = getNuxt() 32 | 33 | const buildFiles: string[] = [] 34 | iterateOnDirectory(options.buildDir, filename => 35 | buildFiles.push(relative(__dirname, filename)) 36 | ) 37 | expect(buildFiles).toContainEqual(expect.stringMatching('nuxt-typed-vuex.js')) 38 | }) 39 | 40 | test('generated files (dist) exist', async () => { 41 | const { options } = getNuxt() 42 | 43 | const generatedFiles: string[] = [] 44 | iterateOnDirectory(options.generate.dir!, filename => 45 | generatedFiles.push(relative(__dirname, filename)) 46 | ) 47 | expect(generatedFiles).toContainEqual( 48 | expect.stringMatching(/dist[\\/]index.html/) 49 | ) 50 | }) 51 | 52 | test('plugin fails without store', async () => { 53 | // @ts-ignore 54 | global.console = { warn: jest.fn() } 55 | const emptyNuxt = new Nuxt({ 56 | ...require('./fixture/nuxt.config'), 57 | srcDir: '.', 58 | }) 59 | await emptyNuxt.ready() 60 | expect(console.warn).toBeCalled() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/test/module.test.ts: -------------------------------------------------------------------------------- 1 | import nuxtModule from '../src' 2 | 3 | import { setupTest, expectModuleToBeCalledWith } from '@nuxt/test-utils' 4 | 5 | describe('nuxt module', () => { 6 | setupTest({ 7 | testDir: __dirname, 8 | }) 9 | it('exists', () => { 10 | expect(nuxtModule).toBeDefined() 11 | }) 12 | it('adds plugin', () => { 13 | expectModuleToBeCalledWith('addPlugin', { 14 | src: expect.stringMatching(/template[\\/]plugin.js/), 15 | fileName: 'nuxt-typed-vuex.js', 16 | options: { 17 | store: expect.stringContaining('store'), 18 | }, 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/nuxt-typed-vuex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "lib" 6 | }, 7 | "exclude": ["node_modules", "lib", "test"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/typed-vuex/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-typescript'], 3 | plugins: ['@babel/plugin-transform-runtime'] 4 | } 5 | -------------------------------------------------------------------------------- /packages/typed-vuex/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['test'], 3 | } 4 | -------------------------------------------------------------------------------- /packages/typed-vuex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-vuex", 3 | "version": "0.3.1", 4 | "description": "A typed store accessor for Vuex.", 5 | "keywords": [ 6 | "vuex", 7 | "nuxt", 8 | "typescript", 9 | "vue", 10 | "vuejs", 11 | "nuxtjs", 12 | "nuxt-module" 13 | ], 14 | "repository": "danielroe/typed-vuex", 15 | "license": "MIT", 16 | "author": { 17 | "name": "Daniel Roe", 18 | "email": "daniel@roe.dev", 19 | "url": "https://github.com/danielroe" 20 | }, 21 | "main": "dist/index.cjs", 22 | "module": "dist/index.mjs", 23 | "types": "dist/index.d.ts", 24 | "files": [ 25 | "dist/**/*", 26 | "dist/index.d.ts", 27 | "!**/*.map" 28 | ], 29 | "scripts": { 30 | "build": "unbuild", 31 | "test:types": "tsd" 32 | }, 33 | "devDependencies": { 34 | "@vue/test-utils": "^1.3.6", 35 | "tsd": "^0.20.0", 36 | "vue": "^2.7.16", 37 | "vuex": "^3.6.2" 38 | }, 39 | "peerDependencies": { 40 | "vuex": "^3.6.2 || ^4.0.0" 41 | }, 42 | "tsd": { 43 | "directory": "test/tsd", 44 | "compilerOptions": { 45 | "rootDir": "." 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/accessor.ts: -------------------------------------------------------------------------------- 1 | import { Store, GetterTree, MutationTree, ActionTree } from 'vuex' 2 | import { NuxtStoreInput, MergedStoreType, BlankStore } from './types/store' 3 | import { State, StateType } from './types/state' 4 | import { NuxtModules } from './types/modules' 5 | 6 | export const getAccessorType = < 7 | T extends State, 8 | G extends GetterTree, any>, 9 | M extends MutationTree>, 10 | A extends ActionTree, any>, 11 | S extends NuxtModules 12 | >( 13 | store: Partial> 14 | ) => { 15 | return (undefined as any) as MergedStoreType 16 | } 17 | 18 | const getNestedState = (parent: any, namespaces: string[]): any => { 19 | if (!parent[namespaces[0]]) { 20 | return parent 21 | } else { 22 | return getNestedState(parent[namespaces[0]], namespaces.slice(1)) 23 | } 24 | } 25 | 26 | const createAccessor = ( 27 | store: Store, 28 | { 29 | getters, 30 | state, 31 | mutations, 32 | actions, 33 | namespaced, 34 | }: Partial>, 35 | namespace = '' 36 | ) => { 37 | const namespacedPath = namespace && namespaced ? `${namespace}/` : '' 38 | const accessor: Record = {} 39 | Object.keys(getters || {}).forEach(getter => { 40 | Object.defineProperty(accessor, getter, { 41 | get: () => store.getters[`${namespacedPath}${getter}`], 42 | }) 43 | }) 44 | const evaluatedState = state 45 | ? state instanceof Function 46 | ? state() 47 | : state 48 | : {} 49 | Object.keys(evaluatedState).forEach(prop => { 50 | if (!Object.getOwnPropertyNames(accessor).includes(prop)) { 51 | const namespaces = namespace.split('/') 52 | Object.defineProperty(accessor, prop, { 53 | get: () => getNestedState(store.state, namespaces)[prop], 54 | }) 55 | } 56 | }) 57 | Object.keys(mutations || {}).forEach(mutation => { 58 | accessor[mutation] = (mutationPayload: any) => 59 | store.commit(`${namespacedPath}${mutation}`, mutationPayload) 60 | }) 61 | Object.keys(actions || {}).forEach(action => { 62 | accessor[action] = (actionPayload: any) => 63 | store.dispatch(`${namespacedPath}${action}`, actionPayload) 64 | }) 65 | return accessor 66 | } 67 | 68 | export const useAccessor = < 69 | T extends State, 70 | G extends GetterTree, any>, 71 | M extends MutationTree>, 72 | A extends ActionTree, any>, 73 | S extends NuxtModules 74 | >( 75 | store: Store, 76 | input: Partial>, 77 | namespace?: string 78 | ) => { 79 | const accessor = createAccessor(store, input, namespace) 80 | Object.keys(input.modules || {}).forEach(moduleNamespace => { 81 | const nestedNamespace = namespace 82 | ? `${namespace}/${moduleNamespace}` 83 | : moduleNamespace 84 | accessor[moduleNamespace] = useAccessor( 85 | store, 86 | (input.modules as any)[moduleNamespace], 87 | nestedNamespace 88 | ) 89 | }) 90 | 91 | const storeType = getAccessorType(input) 92 | 93 | return accessor as typeof storeType 94 | } 95 | 96 | export const getAccessorFromStore = (pattern: any) => { 97 | return (store: Store) => 98 | useAccessor(store, pattern._modules.root._rawModule) 99 | } 100 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types/store' 2 | export * from './types/actions' 3 | export * from './types/getters' 4 | export * from './types/mutations' 5 | export * from './types/utils' 6 | export { useAccessor, getAccessorType, getAccessorFromStore } from './accessor' 7 | export { createMapper } from './utils' 8 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DispatchOptions, 3 | Dispatch, 4 | GetterTree, 5 | MutationTree, 6 | Store, 7 | } from 'vuex' 8 | 9 | import { NuxtStore, NuxtStoreInput } from './store' 10 | import { StateType, State } from './state' 11 | import { GettersTransformer } from './getters' 12 | import { Commit } from './mutations' 13 | import { ModuleTransformer } from './modules' 14 | import { MergedFunctionProcessor } from './utilities' 15 | 16 | // interface Dispatch any>> { 17 | // ( 18 | // action: A, 19 | // payload: StoreParameter, 20 | // options?: DispatchOptions 21 | // ): ReturnType 22 | // ( 23 | // action: StoreParameter extends never ? A : never, 24 | // options?: DispatchOptions 25 | // ): ReturnType 26 | // } 27 | 28 | export type RootStateHelper> = StateType< 29 | T['state'] 30 | > & 31 | ModuleTransformer 32 | 33 | export type RootGettersHelper< 34 | T extends Required 35 | > = GettersTransformer & 36 | ModuleTransformer 37 | 38 | export type ActionContext> = { 39 | state: StateType 40 | getters: { [P in keyof T['getters']]: ReturnType } 41 | commit: Commit 42 | dispatch: Dispatch 43 | rootState: any 44 | rootGetters: any 45 | } 46 | 47 | export type ActionTransformer> = { 48 | [P in keyof T]: MergedFunctionProcessor 49 | } 50 | 51 | interface ActionHandler { 52 | ( 53 | this: Store>, 54 | injectee: ActionContext, 55 | payload?: any 56 | ): any 57 | } 58 | 59 | interface ModifiedActionTree { 60 | [key: string]: ActionHandler 61 | } 62 | 63 | export interface NormalisedActionHandler> { 64 | (this: Store, ...args: Parameters): ReturnType 65 | } 66 | 67 | type NormalisedActionTree> = { 68 | [P in keyof T]: NormalisedActionHandler 69 | } 70 | 71 | export const actionTree = < 72 | S extends State, 73 | G extends GetterTree, any>, 74 | M extends MutationTree>, 75 | T extends ModifiedActionTree>> 76 | >( 77 | _store: NuxtStoreInput, 78 | tree: T 79 | ) => tree as NormalisedActionTree 80 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex' 2 | import { StateType } from './state' 3 | 4 | export type GettersTransformer> = Readonly<{ 5 | [P in keyof T]: ReturnType 6 | }> 7 | 8 | export const getterTree = < 9 | S extends Record, 10 | T extends GetterTree, any> 11 | >( 12 | _state: S, 13 | tree: T 14 | ) => tree 15 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/modules.ts: -------------------------------------------------------------------------------- 1 | import { NuxtStore, MergedStoreType, BlankStore } from './store' 2 | 3 | export type NuxtModules = Record> 4 | 5 | export type ModuleTransformer = T extends NuxtModules 6 | ? { [P in keyof T]: MergedStoreType } 7 | : {} 8 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree, CommitOptions } from 'vuex' 2 | 3 | import { StoreParameter, MergedFunctionProcessor } from './utilities' 4 | import { StateType } from './state' 5 | 6 | export type MutationsTransformer> = { 7 | [P in keyof T]: MergedFunctionProcessor 8 | } 9 | 10 | export interface Commit any>> { 11 |

( 12 | mutation: P, 13 | payload: StoreParameter, 14 | options?: CommitOptions 15 | ): ReturnType 16 |

( 17 | mutation: StoreParameter extends never ? P : never, 18 | options?: CommitOptions 19 | ): ReturnType 20 | } 21 | 22 | export const mutationTree = < 23 | S extends Record, 24 | T extends MutationTree> 25 | >( 26 | _state: S, 27 | tree: T 28 | ) => tree 29 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/state.ts: -------------------------------------------------------------------------------- 1 | import { Not } from './utilities' 2 | 3 | type StateObject = Not, Function> 4 | type StateFunction = Not<() => unknown | any, Record> 5 | 6 | export type State = StateObject | StateFunction 7 | export type StateType = T extends () => any ? ReturnType : T 8 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/store.ts: -------------------------------------------------------------------------------- 1 | import { Store } from 'vuex' 2 | import { State, StateType } from './state' 3 | import { GettersTransformer } from './getters' 4 | import { MutationsTransformer } from './mutations' 5 | import { 6 | ActionTransformer, 7 | ActionContext, 8 | RootStateHelper, 9 | RootGettersHelper, 10 | } from './actions' 11 | import { NuxtModules, ModuleTransformer } from './modules' 12 | 13 | export interface BlankStore { 14 | state: {} 15 | getters: {} 16 | mutations: {} 17 | actions: {} 18 | modules: {} 19 | namespaced: boolean 20 | strict: boolean 21 | } 22 | 23 | export interface NuxtStore { 24 | state: State 25 | getters: Record 26 | mutations: Record 27 | actions: Record 28 | modules: NuxtModules 29 | namespaced: boolean 30 | strict: boolean 31 | } 32 | 33 | export interface NuxtStoreInput< 34 | T extends State, 35 | G, 36 | M, 37 | A, 38 | S extends { [key: string]: Partial } 39 | > { 40 | strict?: boolean 41 | namespaced?: boolean 42 | state: T 43 | getters?: G 44 | mutations?: M 45 | actions?: A 46 | modules?: S 47 | } 48 | 49 | export type MergedStoreType< 50 | T extends NuxtStore, 51 | K = string 52 | > = ('state' extends K ? StateType : {}) & 53 | ('getters' extends K ? GettersTransformer : {}) & 54 | ('mutations' extends K ? MutationsTransformer : {}) & 55 | ('actions' extends K ? ActionTransformer : {}) & 56 | ('modules' extends K ? ModuleTransformer : {}) 57 | 58 | export const getStoreType = ( 59 | store: NuxtStoreInput 60 | ) => { 61 | return { 62 | actionContext: {} as ActionContext, 63 | rootState: {} as RootStateHelper, 64 | rootGetters: {} as RootGettersHelper, 65 | storeInstance: {} as ActionContext & 66 | Exclude>, ActionContext>, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/utilities.ts: -------------------------------------------------------------------------------- 1 | export type Not = T extends M ? never : T 2 | 3 | export type StoreParameter any> = Parameters< 4 | T 5 | >[1] extends undefined 6 | ? never 7 | : Parameters[1] extends NonNullable[1]> 8 | ? Parameters[1] 9 | : Parameters[1] | undefined 10 | 11 | export type MergedFunctionProcessor any, O> = Parameters< 12 | T 13 | >[1] extends undefined 14 | ? (options?: O) => ReturnType 15 | : Parameters[1] extends NonNullable[1]> 16 | ? (payload: Parameters[1], options?: O) => ReturnType 17 | : (payload?: Parameters[1], options?: O) => ReturnType 18 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type ComputedState> = { 2 | [K in keyof T]: T[K] extends Function ? T[K] : () => T[K] 3 | } 4 | 5 | export interface Mapper> { 6 | ( 7 | prop: M, 8 | properties: P[] 9 | ): ComputedState> 10 | ( 11 | prop: M | M[] 12 | ): ComputedState> 13 | } 14 | -------------------------------------------------------------------------------- /packages/typed-vuex/src/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { Mapper } from './types/utils' 3 | 4 | export const createMapper = >(accessor: T) => { 5 | const mapper: Mapper = (prop: any, properties?: string[]) => { 6 | if (!properties) { 7 | return Object.fromEntries( 8 | (Array.isArray(prop) ? prop : [prop]).map(property => [ 9 | property, 10 | function(...args: any[]) { 11 | const value = accessor 12 | ? accessor[property] 13 | : // @ts-ignore 14 | this.$accessor[property] 15 | if (value && typeof value === 'function') return value(...args) 16 | return value 17 | }, 18 | ]) 19 | ) 20 | } 21 | return Object.fromEntries( 22 | properties.map(property => [ 23 | property, 24 | function(...args: any[]) { 25 | const value = 26 | // @ts-ignore 27 | accessor ? accessor[prop][property] : this.$accessor[prop][property] 28 | if (value && typeof value === 'function') return value(...args) 29 | return value 30 | }, 31 | ]) 32 | ) 33 | } 34 | return mapper 35 | } 36 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/__snapshots__/accessor.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`accessor accessor can be generated from store object 1`] = ` 4 | Object { 5 | "initialiseStore": [Function], 6 | "resetEmail": [Function], 7 | "resetEmailWithOptionalPayload": [Function], 8 | "setEmail": [Function], 9 | "submodule": Object { 10 | "initialise": [Function], 11 | "nestedSubmodule": Object { 12 | "initialise": [Function], 13 | "setFirstName": [Function], 14 | "setLastName": [Function], 15 | "setName": [Function], 16 | }, 17 | "setFirstName": [Function], 18 | "setLastName": [Function], 19 | "setName": [Function], 20 | }, 21 | } 22 | `; 23 | 24 | exports[`accessor accessor object exists 1`] = ` 25 | Object { 26 | "initialiseStore": [Function], 27 | "resetEmail": [Function], 28 | "resetEmailWithOptionalPayload": [Function], 29 | "setEmail": [Function], 30 | "submodule": Object { 31 | "initialise": [Function], 32 | "nestedSubmodule": Object { 33 | "initialise": [Function], 34 | "setFirstName": [Function], 35 | "setLastName": [Function], 36 | "setName": [Function], 37 | }, 38 | "setFirstName": [Function], 39 | "setLastName": [Function], 40 | "setName": [Function], 41 | }, 42 | } 43 | `; 44 | 45 | exports[`accessor accessor works with state object 1`] = `""`; 46 | 47 | exports[`accessor accessor works with state object 2`] = `"test1@email.com"`; 48 | 49 | exports[`accessor store exists 1`] = ` 50 | Object { 51 | "actions": Object { 52 | "resetEmail": [Function], 53 | "resetEmailWithOptionalPayload": [Function], 54 | }, 55 | "getters": Object { 56 | "email": [Function], 57 | "fullEmail": [Function], 58 | }, 59 | "modules": Object { 60 | "submodule": Object { 61 | "actions": Object { 62 | "initialise": [Function], 63 | "setName": [Function], 64 | }, 65 | "getters": Object { 66 | "fullName": [Function], 67 | }, 68 | "modules": Object { 69 | "nestedSubmodule": Object { 70 | "actions": Object { 71 | "initialise": [Function], 72 | "setName": [Function], 73 | }, 74 | "getters": Object { 75 | "fullName": [Function], 76 | }, 77 | "mutations": Object { 78 | "setFirstName": [Function], 79 | "setLastName": [Function], 80 | }, 81 | "namespaced": true, 82 | "state": [Function], 83 | }, 84 | }, 85 | "mutations": Object { 86 | "setFirstName": [Function], 87 | "setLastName": [Function], 88 | }, 89 | "namespaced": true, 90 | "state": [Function], 91 | }, 92 | }, 93 | "mutations": Object { 94 | "initialiseStore": [Function], 95 | "setEmail": [Function], 96 | }, 97 | "state": [Function], 98 | } 99 | `; 100 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/__snapshots__/utils.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mapper works within a component with injected accessor 1`] = ` 4 | "email: \\"\\" 5 | firstName: \\"\\" 6 | fullEmail: \\"\\" 7 | fullName: \\" \\"" 8 | `; 9 | 10 | exports[`mapper works within a component with injected accessor 2`] = ` 11 | "

        email: \\"daniel@test.com\\"
12 |         firstName: \\"\\"
13 |         fullEmail: \\"daniel@test.com\\"
14 |         fullName: \\" Smith\\"
15 |       
" 16 | `; 17 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/accessor.test.ts: -------------------------------------------------------------------------------- 1 | import { useAccessor, getAccessorType, getAccessorFromStore } from '../src' 2 | import Vuex, { Store } from 'vuex' 3 | import Vue from 'vue' 4 | 5 | import { getters, state, actions, mutations } from './fixture' 6 | import * as submodule from './fixture/submodule' 7 | 8 | const pattern = { 9 | getters, 10 | state, 11 | actions, 12 | mutations, 13 | modules: { 14 | submodule: { 15 | ...submodule, 16 | namespaced: true, 17 | modules: { 18 | nestedSubmodule: { 19 | ...submodule, 20 | namespaced: true, 21 | }, 22 | }, 23 | }, 24 | }, 25 | } 26 | 27 | const accessorType = getAccessorType(pattern) 28 | const submoduleAccessorType = getAccessorType(submodule) 29 | 30 | const submoduleBehaviour = (accessor: typeof submoduleAccessorType) => { 31 | expect(accessor.firstName).toEqual('') 32 | accessor.setFirstName('Nina') 33 | expect(accessor.firstName).toEqual('Nina') 34 | accessor.setLastName('Willis') 35 | expect(accessor.fullName).toEqual('Nina Willis') 36 | 37 | accessor.initialise() 38 | expect(accessor.fullName).toEqual('John Baker') 39 | accessor.setName('Jordan Lawrence') 40 | expect(accessor.lastName).toEqual('Lawrence') 41 | accessor.setName('George') 42 | expect(accessor.firstName).toEqual('George') 43 | } 44 | 45 | describe('accessor', () => { 46 | let store: Store 47 | let accessor: typeof accessorType 48 | 49 | beforeEach(() => { 50 | Vue.use(Vuex) 51 | store = new Store(pattern) 52 | accessor = useAccessor(store, pattern) 53 | }) 54 | test('store exists', async () => { 55 | expect(pattern).toMatchSnapshot() 56 | }) 57 | test('accessor object exists', async () => { 58 | expect(accessor).toMatchSnapshot() 59 | }) 60 | test('accessor can be generated from store object', async () => { 61 | const accessorFromStore = getAccessorFromStore(store)(store) 62 | expect(accessorFromStore).toMatchSnapshot() 63 | }) 64 | test('accessor works with state object', async () => { 65 | const stateAccessor = useAccessor(store, { 66 | state: { 67 | email: 'this does not matter', 68 | }, 69 | mutations: { 70 | setEmail(state, newValue: string) { 71 | ;(state as any).email = newValue 72 | }, 73 | }, 74 | }) 75 | expect(stateAccessor.email).toMatchSnapshot() 76 | stateAccessor.setEmail('test1@email.com') 77 | expect(stateAccessor.email).toMatchSnapshot() 78 | }) 79 | test('accessor state, getter, mutation and actions work', async () => { 80 | expect(accessor.fullEmail).toEqual('') 81 | expect(accessor.initialiseStore()).toBeUndefined() 82 | accessor.resetEmailWithOptionalPayload('j@j.com') 83 | expect(accessor.fullEmail).toEqual('j@j.com') 84 | accessor.resetEmailWithOptionalPayload() 85 | expect(accessor.fullEmail).toEqual('a@a.com') 86 | 87 | accessor.setEmail('john@j.com') 88 | expect(accessor.email).toEqual('john@j.com') 89 | expect(accessor.fullEmail).toEqual('john@j.com') 90 | 91 | accessor.resetEmail() 92 | expect(accessor.fullEmail).toEqual('a@a.com') 93 | }) 94 | test('accessor submodule state, getter, mutation and actions work', async () => { 95 | submoduleBehaviour(accessor.submodule) 96 | }) 97 | test('namespaced modules work', async () => { 98 | const nonNamespacedPattern = { 99 | getters, 100 | state, 101 | actions, 102 | mutations, 103 | modules: { 104 | submodule: { 105 | ...submodule, 106 | namespaced: false, 107 | }, 108 | }, 109 | } 110 | const nonNamespacedStore = new Store(nonNamespacedPattern) 111 | const nonNamespacedAccessor = useAccessor( 112 | nonNamespacedStore, 113 | nonNamespacedPattern 114 | ) 115 | submoduleBehaviour(nonNamespacedAccessor.submodule) 116 | }) 117 | test('nested modules work', async () => { 118 | submoduleBehaviour(accessor.submodule.nestedSubmodule) 119 | }) 120 | test('namespaced dynamic modules work', async () => { 121 | store.registerModule('dynamicSubmodule', { ...submodule, namespaced: true }) 122 | const dynamicAccessor = useAccessor(store, { 123 | modules: { dynamicSubmodule: { ...submodule, namespaced: true } }, 124 | }) 125 | 126 | submoduleBehaviour(dynamicAccessor.dynamicSubmodule) 127 | 128 | store.unregisterModule('dynamicSubmodule') 129 | }) 130 | test('dynamic module helper function works', async () => { 131 | store.registerModule('dynamicSubmodule', submodule) 132 | const dynamicAccessor = useAccessor(store, submodule, 'dynamicSubmodule') 133 | 134 | submoduleBehaviour(dynamicAccessor) 135 | store.unregisterModule('dynamicSubmodule') 136 | }) 137 | }) 138 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/fixture/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vuex' 2 | import { 3 | getStoreType, 4 | getAccessorType, 5 | mutationTree, 6 | actionTree, 7 | } from '../../src' 8 | 9 | import * as submodule from './submodule' 10 | 11 | export const state = () => ({ 12 | email: '', 13 | }) 14 | 15 | type RootState = ReturnType 16 | 17 | export const getters = { 18 | email: (state: RootState) => state.email, 19 | fullEmail: (state: RootState) => state.email, 20 | } 21 | 22 | export const mutations = mutationTree(state, { 23 | setEmail(state, newValue: string) { 24 | state.email = newValue 25 | }, 26 | 27 | initialiseStore() { 28 | console.log('initialised') 29 | }, 30 | }) 31 | 32 | export const actions = actionTree( 33 | { state, getters, mutations }, 34 | { 35 | async resetEmail({ commit }) { 36 | commit('setEmail', 'a@a.com') 37 | }, 38 | async resetEmailWithOptionalPayload({ commit }, optionalPayload?: string) { 39 | commit('setEmail', optionalPayload || 'a@a.com') 40 | }, 41 | } 42 | ) 43 | 44 | export const plugins: Plugin[] = [] 45 | 46 | export const pattern = { 47 | actions, 48 | getters, 49 | mutations, 50 | state, 51 | modules: { 52 | submodule, 53 | }, 54 | } 55 | 56 | export const storeType = getStoreType(pattern) 57 | 58 | export const accessorType = getAccessorType(pattern) 59 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/fixture/submodule.ts: -------------------------------------------------------------------------------- 1 | import { getterTree, mutationTree, actionTree, useAccessor } from '../../src' 2 | import { pattern } from '.' 3 | 4 | export const state = () => ({ 5 | firstName: '', 6 | lastName: '', 7 | }) 8 | 9 | export const getters = getterTree(state, { 10 | fullName: (state) => state.firstName + ' ' + state.lastName, 11 | }) 12 | 13 | export const mutations = mutationTree(state, { 14 | setFirstName(state, newValue: string) { 15 | state.firstName = newValue 16 | }, 17 | setLastName(state, newValue: string) { 18 | state.lastName = newValue 19 | }, 20 | }) 21 | 22 | export const actions = actionTree( 23 | { state, getters, mutations }, 24 | { 25 | initialise({ commit }) { 26 | const accessor = useAccessor(this, pattern) 27 | commit('setFirstName', 'John' + accessor.email.split('@')[0]) 28 | commit('setLastName', 'Baker') 29 | }, 30 | setName({ commit }, newName: string) { 31 | const names = newName.split(' ') 32 | commit('setFirstName', names[0]) 33 | if (names.length > 1) commit('setLastName', names[1]) 34 | }, 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/tsd/accessor.test-d.ts: -------------------------------------------------------------------------------- 1 | import { DispatchOptions, CommitOptions } from 'vuex' 2 | import { expectType, expectError } from 'tsd' 3 | 4 | import { getAccessorType } from '../..' 5 | import { getters, state, actions, mutations } from '../fixture' 6 | 7 | import * as submodule from '../fixture/submodule' 8 | 9 | const pattern = { 10 | getters, 11 | state, 12 | actions, 13 | mutations, 14 | modules: { 15 | submodule: { 16 | ...submodule, 17 | namespaced: true, 18 | modules: { 19 | nestedSubmodule: { 20 | ...submodule, 21 | namespaced: true, 22 | }, 23 | }, 24 | }, 25 | }, 26 | } 27 | 28 | const accessorType = getAccessorType(pattern) 29 | const submoduleAccessorType = getAccessorType(submodule) 30 | 31 | expectType(accessorType.fullEmail) 32 | expectType(accessorType.submodule.fullName) 33 | expectType(accessorType.submodule.lastName) 34 | interface CommitFunction { 35 | (payload: string, options?: CommitOptions): void 36 | } 37 | expectType(accessorType.setEmail) 38 | 39 | interface ActionFunction { 40 | (options?: DispatchOptions): Promise 41 | } 42 | interface ActionFunctionWithOptionalPayload { 43 | (payload?: string | undefined, options?: DispatchOptions): Promise 44 | } 45 | expectType(accessorType.resetEmail) 46 | expectType( 47 | accessorType.resetEmailWithOptionalPayload 48 | ) 49 | 50 | expectType(submoduleAccessorType.firstName) 51 | expectType(submoduleAccessorType.initialise()) 52 | expectError(submoduleAccessorType.initialise('argument')) 53 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/tsd/map-state.test-d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { getAccessorType, createMapper } from '../..' 3 | import { expectType } from 'tsd' 4 | 5 | import { getters, state, actions, mutations } from '../fixture' 6 | 7 | import * as submodule from '../fixture/submodule' 8 | import { CommitOptions, DispatchOptions } from 'vuex/types/index' 9 | 10 | const pattern = { 11 | getters, 12 | state, 13 | actions, 14 | mutations, 15 | modules: { 16 | submodule: { 17 | ...submodule, 18 | namespaced: true, 19 | modules: { 20 | nestedSubmodule: { 21 | ...submodule, 22 | namespaced: true, 23 | }, 24 | }, 25 | }, 26 | }, 27 | } 28 | 29 | const accessor = getAccessorType(pattern) 30 | const mapper = createMapper(accessor) 31 | 32 | Vue.extend({ 33 | computed: { 34 | ...mapper(['email']), 35 | ...mapper('fullEmail'), 36 | ...mapper('submodule', ['fullName']), 37 | }, 38 | methods: { 39 | ...mapper('submodule', ['setFirstName', 'setName']), 40 | ...mapper(['resetEmail', 'initialiseStore']), 41 | test(): void { 42 | // actions 43 | expectType<(options?: DispatchOptions | undefined) => Promise>( 44 | this.resetEmail 45 | ) 46 | expectType< 47 | (payload: string, options?: DispatchOptions | undefined) => void 48 | >(this.setName) 49 | // getter 50 | expectType(this.fullEmail) 51 | expectType(this.fullName) 52 | // state 53 | expectType(this.email) 54 | // mutations 55 | expectType< 56 | (payload: string, options?: CommitOptions | undefined) => void 57 | >(this.setFirstName) 58 | expectType<(options?: CommitOptions | undefined) => void>( 59 | this.initialiseStore 60 | ) 61 | }, 62 | }, 63 | }) 64 | -------------------------------------------------------------------------------- /packages/typed-vuex/test/utils.test.ts: -------------------------------------------------------------------------------- 1 | /** @jest-environment jsdom */ 2 | 3 | import { useAccessor, getAccessorType, createMapper, Mapper } from '../src' 4 | import { mount, createLocalVue } from '@vue/test-utils' 5 | import Vuex, { Store } from 'vuex' 6 | 7 | import { getters, state, actions, mutations } from './fixture' 8 | import * as submodule from './fixture/submodule' 9 | 10 | const pattern = { 11 | getters, 12 | state, 13 | actions, 14 | mutations, 15 | modules: { 16 | submodule: { 17 | ...submodule, 18 | namespaced: true, 19 | modules: { 20 | nestedSubmodule: { 21 | ...submodule, 22 | namespaced: true, 23 | }, 24 | }, 25 | }, 26 | }, 27 | } 28 | 29 | const accessorType = getAccessorType(pattern) 30 | 31 | describe('mapper', () => { 32 | let store: Store 33 | let accessor: typeof accessorType 34 | let mapper: Mapper 35 | let Vue 36 | // let nuxtMapper: Mapper 37 | 38 | beforeEach(() => { 39 | Vue = createLocalVue() 40 | Vue.use(Vuex) 41 | store = new Store(pattern) 42 | accessor = useAccessor(store, pattern) 43 | mapper = createMapper(accessor) 44 | }) 45 | 46 | test('mapper works with state', () => { 47 | expect(mapper('email').email()).toEqual('') 48 | expect(mapper('submodule', ['firstName']).firstName()).toEqual('') 49 | }) 50 | test('mapper works with getters', () => { 51 | expect(mapper('fullEmail').fullEmail()).toEqual('') 52 | expect(mapper('submodule', ['fullName']).fullName()).toEqual(' ') 53 | }) 54 | test('mapper works with mutations', () => { 55 | mapper('setEmail').setEmail('example@email.com') 56 | expect(accessor.email).toEqual('example@email.com') 57 | mapper('submodule', ['setLastName']).setLastName('Smith') 58 | expect(accessor.submodule.fullName).toEqual(' Smith') 59 | }) 60 | test('mapper works with actions', () => { 61 | mapper('resetEmail').resetEmail() 62 | expect(accessor.email).toEqual('a@a.com') 63 | }) 64 | test('works within a component with injected accessor', async () => { 65 | const mapper = createMapper(accessorType) 66 | Vue.prototype.$accessor = accessor 67 | const Component = { 68 | template: `
69 |         email: {{ JSON.stringify(email) }}
70 |         firstName: {{ JSON.stringify(firstName) }}
71 |         fullEmail: {{ JSON.stringify(fullEmail) }}
72 |         fullName: {{ JSON.stringify(fullName) }}
73 |       
`, 74 | computed: { 75 | ...mapper('email'), 76 | ...mapper('submodule', ['firstName']), 77 | ...mapper('fullEmail'), 78 | ...mapper('submodule', ['fullName']), 79 | }, 80 | methods: { 81 | ...mapper(['setEmail', 'resetEmail']), 82 | ...mapper('submodule', ['setLastName']), 83 | }, 84 | } 85 | const wrapper = mount(Component, { 86 | store, 87 | localVue: Vue, 88 | }) 89 | expect(wrapper.text()).toMatchSnapshot() 90 | wrapper.vm.setEmail('daniel@test.com') 91 | wrapper.vm.setLastName('Smith') 92 | await Vue.nextTick() 93 | 94 | expect(wrapper.html()).toMatchSnapshot() 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /packages/typed-vuex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "lib": ["esnext", "dom"] 7 | }, 8 | "exclude": ["node_modules", "lib", "test"] 9 | } 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "docs" 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: 'es5', 5 | } 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>danielroe/renovate" 4 | ], 5 | "ignoreDeps": [ 6 | "docus", 7 | "vue", 8 | "vuex" 9 | ], 10 | "$schema": "https://docs.renovatebot.com/renovate-schema.json" 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "sourceMap": true, 5 | "resolveJsonModule": true, 6 | "declaration": true, 7 | "target": "esnext", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "docs/package.json", 5 | "use": "@vercel/static-build" 6 | } 7 | ], 8 | "routes": [{ "handle": "filesystem" }, { "src": "/(.*)", "dest": "/docs/$1" }] 9 | } 10 | --------------------------------------------------------------------------------