{"id":17004833,"url":"https://github.com/clickermonkey/vuex-typescript-interface","last_synced_at":"2025-03-22T16:30:51.176Z","repository":{"id":152212260,"uuid":"181616279","full_name":"ClickerMonkey/vuex-typescript-interface","owner":"ClickerMonkey","description":"Adding magical type safety to Vuex without additional code","archived":false,"fork":false,"pushed_at":"2024-10-24T20:15:06.000Z","size":49,"stargazers_count":44,"open_issues_count":7,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-18T13:32:30.276Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ClickerMonkey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-16T04:45:59.000Z","updated_at":"2023-06-07T15:44:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"c2f68848-e9e7-4d4a-a320-610cc0ac3056","html_url":"https://github.com/ClickerMonkey/vuex-typescript-interface","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/ClickerMonkey%2Fvuex-typescript-interface","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fvuex-typescript-interface/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fvuex-typescript-interface/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickerMonkey%2Fvuex-typescript-interface/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ClickerMonkey","download_url":"https://codeload.github.com/ClickerMonkey/vuex-typescript-interface/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244986346,"owners_count":20543000,"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":[],"created_at":"2024-10-14T04:44:35.015Z","updated_at":"2025-03-22T16:30:51.164Z","avatar_url":"https://github.com/ClickerMonkey.png","language":"TypeScript","readme":"## vuex-typescript-interface\r\n\r\nThrough the magic of Typescript, this library makes Vuex as typesafe as currently possible through a slightly new syntax. This library overrides the types that come with Vuex and provides a slightly altered version.\r\n\r\n### Usage\r\n\r\n```typescript\r\nimport Vuex from 'vuex-typescript-interface';\r\n\r\n// store = new Vuex.Store like normal\r\n\r\nVue.use(Vuex); // works\r\n\r\nnew Vue({\r\n  i18n,\r\n  router,\r\n  store,\r\n  render: h =\u003e h(App)\r\n}).$mount('#app')\r\n```\r\n\r\nNormally you can pass a state to `new Vuex.Store\u003cState\u003e`. That has very limited use. With this library you can pass an interface like this to `Vuex.Store`:\r\n\r\n```typescript\r\ninterface IStore\r\n{\r\n  // State (are non-function properties not marked readonly)\r\n  firstName: string;\r\n  lastName: string;\r\n  // Getters (are non-function properties marked readonly)\r\n  readonly fullName: string;\r\n  // Mutations (functions return void and optionally accept payload)\r\n  clearNames (): void;\r\n  setFirstName (firstName: string): void;\r\n  setLastName (lastName: string): void;\r\n  // Actions (functions return Promise and optionally accept payload)\r\n  loadName (url: string): Promise\u003c{firstName: string, lastName: string}\u003e\r\n}\r\n\r\nconst store = new Vuex.Store\u003cIStore\u003e({\r\n  // if state detected on IStore, this will be required and all state defined\r\n  state: {\r\n    firstName: '',\r\n    lastName: '',\r\n  },\r\n  // if getters detected on IStore, this will be required and all getters defined\r\n  getters: {\r\n     // all arguments here are type safe, and the return type matches what was in IStore (string)\r\n    fullName (state, getters, rootState, rootGetters) {\r\n      return state.firstName + ' ' + state.lastName;\r\n    }\r\n  },\r\n  // if mutations detected on IStore, this will be required and all mutations defined\r\n  mutations: {\r\n    // all arguments are type safe here as well\r\n    clearNames (state) {\r\n      state.firstName = state.lastName = '';\r\n    },\r\n    setFirstName (state, firstName) {\r\n      state.firstName = firstName;\r\n    },\r\n    setLastName (state, lastName) {\r\n      state.lastName = lastName\r\n    }\r\n  },\r\n  // if actions detected on IStore, this will be required and all actions defined\r\n  actions: {\r\n    // all arguments are type safe here as well\r\n    async loadName ({ commit }, url: string) {\r\n      const response = await fetch(url);\r\n      if (response.ok) {\r\n        const names = await response.json();\r\n        commit('setFirstName', names.firstName);\r\n        commit('setLastName', names.lastName);\r\n        return names;\r\n      } else {\r\n        throw 'Invalid URL';\r\n      }\r\n    }\r\n  }\r\n});\r\n\r\nstore.commit('clearNames'); // TS won't allow you to enter incorrect mutation names\r\nstore.commit('setFirstName', 'Phil'); // TS will only allow a string payload\r\n\r\n// all types here as well!\r\nconst { firstName, lastName } = await store.dispatch('loadName', 'http://myname.com');\r\n```\r\n\r\n## Modules\r\n\r\nIt works with modules as well! (just not namespaced ones)\r\n\r\n```typescript\r\nimport Vue, { Module } from 'vuex-typescript-interface';\r\n\r\ninterface IRoot {\r\n  name: string;\r\n  setName (name: string): void;\r\n  modules: {\r\n    child: IChild\r\n  }\r\n}\r\ninterface IChild {\r\n  readonly lowerName: string;\r\n  clearName (): Promise\u003cvoid\u003e\r\n}\r\n\r\nconst child: Module\u003cIChild, IRoot\u003e = {\r\n  getters: {\r\n    lowerName (state, getters, rootState) { // talking to rootState is type safe\r\n      return rootState.name.toLowerCase();\r\n    }\r\n  },\r\n  actions: {\r\n    async clearName ({ commit }) {\r\n      commit('setName', '', { root: true }); // talking to root is type safe\r\n    }\r\n  }\r\n};\r\n\r\nconst root = new Vuex.Store\u003cIRoot\u003e({\r\n  state: {\r\n    name: ''\r\n  },\r\n  mutations: {\r\n    setName (state, name) {\r\n      state.name = name;\r\n    }\r\n  },\r\n  modules: { child } // you could pass objects here like normal\r\n})\r\n```\r\n\r\n## Advanced Modules\r\n\r\nWhen you add modules, you add a property to the state of the store. If you add sub modules, you add a property to the state of that module. All of this is accessible and type safe.\r\n\r\n```typescript\r\n// In this example we can use one big interface if we want.\r\ninterface IStore {\r\n  user: string | null;\r\n  setUser (user: string | null): void;\r\n  modules: {\r\n    auth: {\r\n      logout (): Promise\u003cvoid\u003e;\r\n      login (creds: {username: string, password: string}): Promise\u003cboolean\u003e;\r\n    },\r\n    profile: {\r\n      email: string | null;\r\n      created_at: Date | null;\r\n      readonly age: number;\r\n      setProfile (profile?: { email: string, created_at: Date }): void;\r\n    }\r\n  }\r\n};\r\n\r\n// Mmmm, delicious type safety everywhere.\r\n// The new types require options when applicable, with the required properties and types.\r\nconst store = new Vuex.Store\u003cIStore\u003e({\r\n  state: {\r\n    user: null\r\n  },\r\n  mutations: {\r\n    setUser (state, user) {\r\n      state.user = user;\r\n      if (!user) {\r\n        state.profile.email = null;\r\n        state.profile.created_at = null;\r\n      }\r\n    }\r\n  },\r\n  modules: {\r\n    auth: {\r\n      actions: {\r\n        async logout ({ commit }) {\r\n          commit('setUser', null, { root: true });\r\n        },\r\n        async login ({ commit }, creds) {\r\n          // get user from API\r\n          const user = { id: 'id', email: 'email', created_at: new Date() };\r\n\r\n          commit('setUser', user.id, { root: true });\r\n          commit('setProfile', user, { root: true });\r\n          return true;\r\n        }\r\n      }\r\n    },\r\n    profile: {\r\n      state: {\r\n        email: null,\r\n        created_at: null\r\n      },\r\n      getters: {\r\n        age (state) {\r\n          return state.created_at ? new Date().getFullYear() - state.created_at.getFullYear() : 0;\r\n        }\r\n      },\r\n      mutations: {\r\n        setProfile (state, profile) {\r\n          state.email = profile ? profile.email : null;\r\n          state.created_at = profile ? profile.created_at : null;\r\n        }\r\n      }\r\n    }\r\n  }\r\n});\r\n\r\n```\r\n\r\n## Mapping\r\n\r\nTo take advantage of that sweet type safety for mapping you can use `createNamespacedHelpers` or `createHelpers` (which is the only code this library adds to your code base, just a few bytes).\r\n\r\nYou should use `createHelpers` instead of `mapState`, `mapGetters`, `mapActions`, and `mapMutations` since propery types cannot be added to them at this time.\r\n\r\n```typescript\r\n// These functions are type safe (require valid state/getter/mutations/actions)\r\nconst { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers\u003cIStore\u003e('moduleNamespace');\r\nconst { mapState, mapGetters, mapMutations, mapActions } = createHelpers\u003cIStore\u003e();\r\n\r\nmapState(['firstName'])\r\n// { firstName (): string }\r\n\r\nmapState({\r\n  first: 'firstName',\r\n  hasFullName (state, getters): boolean {\r\n    return state.lastName \u0026\u0026 state.firstName;\r\n  }\r\n})\r\n// { first (): string, hasFullName (): boolean }\r\n\r\nmapGetters(['fullName'])\r\n// { fullName (): string }\r\n\r\nmapGetters({\r\n  full: 'fullName'\r\n})\r\n// { full (): string }\r\n\r\nmapMutations(['clearNames', 'setLastName'])\r\n// { clearNames (): void, setLastName (string): void }\r\n\r\nmapMutations({\r\n  setFirst: 'setFirstName',\r\n  setFirstLower (commit, name: string, lower: boolean = false): void {\r\n    commit('setFirstName', lower ? name.toLowerCase() : name);\r\n  }\r\n})\r\n// { setFirst (first): void, setFirstLower (name, lower?): void }\r\n\r\nmapActions(['loadName'])\r\n// { loadName (url): Promise\u003c{firstName: string, lastName: string}\u003e }\r\n\r\nmapActions({\r\n  load: 'loadName',\r\n  async getFirst (dispatch, url: string, upper: boolean = false): Promise\u003cstring\u003e {\r\n    const { firstName } = await dispatch('loadName', url);\r\n    return upper ? firstName.toUpperCase() : firstName;\r\n  }\r\n})\r\n// { \r\n//   load (url): Promise\u003c{firstName: string, lastName: string}\u003e\r\n//   getFirst (url, upper): Promise\u003cstring\u003e\r\n// }\r\n```\r\n\r\n\r\n### Restrictions\r\n- Namespaced modules don't work.\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclickermonkey%2Fvuex-typescript-interface","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclickermonkey%2Fvuex-typescript-interface","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclickermonkey%2Fvuex-typescript-interface/lists"}