{"id":19256473,"url":"https://github.com/paroi-tech/direct-vuex","last_synced_at":"2025-04-05T01:05:56.318Z","repository":{"id":49751000,"uuid":"214692839","full_name":"paroi-tech/direct-vuex","owner":"paroi-tech","description":"Use and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.","archived":false,"fork":false,"pushed_at":"2021-02-24T10:33:16.000Z","size":286,"stargazers_count":258,"open_issues_count":15,"forks_count":14,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-29T00:09:10.988Z","etag":null,"topics":["typescript","vuex"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paroi-tech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.txt","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-12T18:07:38.000Z","updated_at":"2025-03-21T03:15:13.000Z","dependencies_parsed_at":"2022-08-27T03:50:27.718Z","dependency_job_id":null,"html_url":"https://github.com/paroi-tech/direct-vuex","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paroi-tech%2Fdirect-vuex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paroi-tech%2Fdirect-vuex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paroi-tech%2Fdirect-vuex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paroi-tech%2Fdirect-vuex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paroi-tech","download_url":"https://codeload.github.com/paroi-tech/direct-vuex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271528,"owners_count":20911587,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["typescript","vuex"],"created_at":"2024-11-09T19:05:52.116Z","updated_at":"2025-04-05T01:05:56.286Z","avatar_url":"https://github.com/paroi-tech.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# direct-vuex\n\n[![Build Status](https://travis-ci.com/paroi-tech/direct-vuex.svg?branch=master)](https://travis-ci.com/paroi-tech/direct-vuex)\n[![Dependencies Status](https://david-dm.org/paroi-tech/direct-vuex/status.svg)](https://david-dm.org/paroi-tech/direct-vuex)\n[![npm](https://img.shields.io/npm/dm/direct-vuex)](https://www.npmjs.com/package/direct-vuex)\n![Type definitions](https://img.shields.io/npm/types/direct-vuex)\n[![GitHub](https://img.shields.io/github/license/paroi-tech/direct-vuex)](https://github.com/paroi-tech/direct-vuex)\n\nUse and implement your Vuex store with TypeScript types. Direct-vuex doesn't require classes, therefore it is compatible with the Vue 3 composition API.\n\n## Install\n\nFirst, add `direct-vuex` to a **Vue 2** application:\n\n```sh\nnpm install direct-vuex\n```\n\nOr, in a **Vue 3** application:\n\n```sh\nnpm install direct-vuex@next\n```\n\n## Create the store\n\nThe store can be implemented almost in the same way as usual.\n\nCreate the store:\n\n```ts\nimport Vue from \"vue\"\nimport Vuex from \"vuex\"\nimport { createDirectStore } from \"direct-vuex\"\n\nVue.use(Vuex)\n\nconst {\n  store,\n  rootActionContext,\n  moduleActionContext,\n  rootGetterContext,\n  moduleGetterContext\n} = createDirectStore({\n  // … store implementation here …\n})\n\n// Export the direct-store instead of the classic Vuex store.\nexport default store\n\n// The following exports will be used to enable types in the\n// implementation of actions and getters.\nexport {\n  rootActionContext,\n  moduleActionContext,\n  rootGetterContext,\n  moduleGetterContext\n}\n\n// The following lines enable types in the injected store '$store'.\nexport type AppStore = typeof store\ndeclare module \"vuex\" {\n  interface Store\u003cS\u003e {\n    direct: AppStore\n  }\n}\n```\n\nThe classic Vuex store is still accessible through the `store.original` property. We need it to initialize the Vue application:\n\n```ts\nimport Vue from \"vue\"\nimport store from \"./store\"\n\nnew Vue({\n  store: store.original, // Inject the classic Vuex store.\n  // …\n}).$mount(\"#app\")\n```\n\n## Use typed wrappers from outside the store\n\nFrom a component, the direct store is accessible through the `direct` property of the classic store:\n\n```ts\nconst store = context.root.$store.direct // or: this.$store.direct\n```\n\nOr, you can just import it:\n\n```ts\nimport store from \"./store\"\n```\n\nThen, the old way to call an action:\n\n```ts\nstore.dispatch(\"mod1/myAction\", myPayload)\n```\n\n… is replaced by the following wrapper:\n\n```ts\nstore.dispatch.mod1.myAction(myPayload)\n```\n\n… which is fully typed.\n\nTyped getters and mutations are accessible the same way:\n\n```ts\nstore.getters.mod1.myGetter\nstore.commit.mod1.myMutation(myPayload)\n```\n\nNotice: The underlying Vuex store can be used simultaneously if you wish, through the injected `$store` or `store.original`.\n\n## A limitation on how to declare a State\n\nIn store and module options, the `state` property shouldn't be declared with the ES6 method syntax.\n\nValid:\n\n```ts\n  state: { p1: string } as Mod1State\n```\n\n```ts\n  state: (): Mod1State =\u003e { p1: string }\n```\n\n```ts\n  state: function (): Mod1State { return { p1: string } }\n```\n\nInvalid:\n\n```ts\n  state(): Mod1State { return { p1: string } }\n```\n\nI'm not sure why but TypeScript doesn't infer the state type correctly when we write that.\n\n## Implement a Vuex Store with typed helpers\n\nDirect-vuex provides several useful helpers for implementation of the store. They are all optional. However, if you want to keep your classic implementation of a Vuex Store, then direct-vuex needs to infer the literal type of the `namespaced` property. You can write `namespaced: true as true` where there is a `namespaced` property. But you don't need to worry about that if you use `defineModule`.\n\n### In a Vuex Module\n\nThe function `defineModule` is provided solely for type inference. It is a no-op behavior-wise. It expects a module implementation and returns the argument as-is. This behaviour is similar to (and inspired from) the [function `defineComponent`](https://vue-composition-api-rfc.netlify.com/api.html#definecomponent) from the composition API.\n\nThe generated functions `moduleActionContext` and `moduleGetterContext` are factories for creating functions `mod1ActionContext` and `mod1GetterContext`, which converts injected action and getter contexts to their direct-vuex equivalent.\n\nHere is how to use `defineModule`, `moduleActionContext` and `moduleGetterContext`:\n\n```ts\nimport { defineModule } from \"direct-vuex\"\nimport { moduleActionContext, moduleGetterContext } from \"./store\"\n\nexport interface Mod1State {\n  p1: string\n}\n\nconst mod1 = defineModule({\n  state: (): Mod1State =\u003e {\n    return {\n      p1: \"\"\n    }\n  },\n  getters: {\n    p1OrDefault(...args): string {\n      const { state, getters, rootState, rootGetters } = mod1GetterContext(args)\n      // Here, 'getters', 'state', 'rootGetters' and 'rootState' are typed.\n      // Without 'mod1GetterContext' only 'state' would be typed.\n      return state.p1 || \"default\"\n    }\n  },\n  mutations: {\n    SET_P1(state, p1: string) {\n      // Here, the type of 'state' is 'Mod1State'.\n      state.p1 = p1\n    }\n  },\n  actions: {\n    loadP1(context, payload: { id: string }) {\n      const { dispatch, commit, getters, state } = mod1ActionContext(context)\n      // Here, 'dispatch', 'commit', 'getters' and 'state' are typed.\n    }\n  },\n})\n\nexport default mod1\nconst mod1GetterContext = (args: [any, any, any, any]) =\u003e moduleGetterContext(args, mod1)\nconst mod1ActionContext = (context: any) =\u003e moduleActionContext(context, mod1)\n```\n\n2 Warnings:\n\n* Types in the context of actions implies that TypeScript should never infer the return type of an action from the context of the action. Indeed, this kind of typing would be recursive, since the context includes the return value of the action. When this happens, TypeScript passes the whole context to `any`. Tl;dr; **Declare the return type of actions where it exists!**\n* For the same reason, **declare the return type of getters each time a getter context generated by `moduleGetterContext` is used!**\n\n### Get the typed context of a Vuex Getter, but in the root store\n\nThe generated function `rootGetterContext` converts the injected action context to the direct-vuex one, at the root level (not in a module).\n\n```ts\n  getters: {\n    getterInTheRootStore(...args) {\n      const { state, getters } = rootGetterContext(args)\n      // Here, 'getters', 'state' are typed.\n      // Without 'rootGetterContext' only 'state' would be typed.\n    }\n  }\n```\n\n### Get the typed context of a Vuex Action, but in the root store\n\nThe generated function `rootActionContext` converts the injected action context to the direct-vuex one, at the root level (not in a module).\n\n```ts\n  actions: {\n    async actionInTheRootStore(context, payload) {\n      const { commit, state } = rootActionContext(context)\n      // … Here, 'commit' and 'state' are typed.\n    }\n  }\n```\n\n### Alternatively: Use `localGetterContext` and `localActionContext`\n\nInstead of `moduleActionContext` and `moduleGetterContext`, which imply circular dependencies, it is possible to use `localGetterContext` and `localActionContext`:\n\n```ts\nimport { defineModule, localActionContext, localGetterContext } from \"direct-vuex\"\n\nconst mod1 = defineModule({\n  // …\n})\n\nexport default mod1\nconst mod1GetterContext = (args: [any, any, any, any]) =\u003e localGetterContext(args, mod1)\nconst mod1ActionContext = (context: any) =\u003e localActionContext(context, mod1)\n```\n\nNow there isn't circular dependency, but getter and action contexts don't provide access to `rootState`, `rootGetters`, `rootCommit`, `rootDispatch`.\n\nFunctions `localGetterContext` and `localActionContext` can be used in place of `rootGetterContext` and `rootActionContext` too.\n\n### Use `defineGetters`\n\nThe function `defineGetters` is provided solely for type inference. It is a no-op behavior-wise. It is a factory for a function, which expects the object of a `getters` property and returns the argument as-is.\n\n```ts\nimport { defineGetters } from \"direct-vuex\"\nimport { Mod1State } from \"./mod1\" // Import the local definition of the state (for example from the current module)\n\nexport default defineGetters\u003cMod1State\u003e()({\n  getter1(...args) {\n    const { state, getters, rootState, rootGetters } = mod1GetterContext(args)\n    // Here, 'getters', 'state', 'rootGetters' and 'rootState' are typed.\n    // Without 'mod1GetterContext' only 'state' would be typed.\n  },\n})\n```\n\nNote: There is a limitation. The second parameters `getters` in a getter implementation, is not typed.\n\n### Use `defineMutations`\n\nThe function `defineMutations` is provided solely for type inference. It is a no-op behavior-wise. It is a factory for a function, which expects the object of a `mutations` property and returns the argument as-is.\n\n```ts\nimport { defineMutations } from \"direct-vuex\"\nimport { Mod1State } from \"./mod1\" // Import the local definition of the state (for example from the current module)\n\nexport default defineMutations\u003cMod1State\u003e()({\n  SET_P1(state, p1: string) {\n    // Here, the type of 'state' is 'Mod1State'.\n    state.p1 = p1\n  }\n})\n```\n\n### Use `defineActions`\n\nThe function `defineActions` is provided solely for type inference. It is a no-op behavior-wise. It expects the object of an `actions` property and returns the argument as-is.\n\n```ts\nimport { defineActions } from \"direct-vuex\"\n\nexport default defineActions({\n  loadP1(context, payload: { id: string }) {\n    const { dispatch, commit, getters, state } = mod1ActionContext(context)\n    // Here, 'dispatch', 'commit', 'getters' and 'state' are typed.\n  }\n})\n```\n\n## About Direct-vuex and Circular Dependencies\n\nWhen the helper `moduleActionContext` and `moduleGetterContext` are used, linters may warn about an issue: _\"Variable used before it's assigned\"_. I couldn't avoid circular dependencies. Action contexts and getter contexts need to be inferred at the store level, because they contain `rootState` etc.\n\nHere is an example of a Vuex module implementation:\n\n```ts\nimport { moduleActionContext } from \"./store\"\n\nconst mod1 = {\n  getters: {\n    p1OrDefault(...args) {\n      const { state, getters, rootState, rootGetters } = mod1GetterContext(args)\n      // …\n    }\n  },\n  actions: {\n    loadP1(context, payload: { id: string }) {\n      const { commit, rootState } = mod1ActionContext(context)\n      // …\n    }\n  }\n}\n\nexport default mod1\nconst mod1ActionContext = (context: any) =\u003e moduleActionContext(context, mod1)\nconst mod1GetterContext = (args: [any, any, any, any]) =\u003e moduleGetterContext(args, mod1)\n```\n\nIt works because `mod1ActionContext` (or `mod1GetterContext`) is not executed at the same time it is declared. It is executed when an action (or a getter) is executed, ie. after all the store and modules are already initialized.\n\nI suggest to disable the linter rule with a comment at the top of the source file.\n\nWith TSLint:\n\n```js\n// tslint:disable: no-use-before-declare\n```\n\nWith ESLint:\n\n```js\n/* eslint-disable no-use-before-define */\n```\n\n**Notice: A consequence of these circular dependencies is that _the main store file must be imported first_ from the rest of the application. If a Vuex module is imported first, some part of your implementation could be `undefined` at runtime.**\n\n## Contribute\n\nWith VS Code, our recommended plugin is:\n\n* **TSLint** from Microsoft (`ms-vscode.vscode-typescript-tslint-plugin`)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparoi-tech%2Fdirect-vuex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparoi-tech%2Fdirect-vuex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparoi-tech%2Fdirect-vuex/lists"}