├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── CHANGELOG.txt
├── LICENSE
├── README.md
├── build
└── make-bundles.js
├── jest.config.js
├── package-lock.json
├── package.json
├── src
└── direct-vuex.ts
├── tests
├── action-context.spec.ts
├── namespaced.spec.ts
└── non-namespaced.spec.ts
├── tsconfig.json
├── tslint.json
└── types
├── direct-types.d.ts
└── index.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | ._*
3 |
4 | node_modules
5 | .npmrc
6 |
7 | /build/compiled-*
8 | /dist
9 |
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 | language: node_js
3 | node_js:
4 | - 10
5 | - 12
6 | cache:
7 | directories:
8 | - "node_modules"
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[typescript]": {
3 | "editor.codeActionsOnSave": {
4 | "source.fixAll": true,
5 | "source.organizeImports": true
6 | },
7 | "editor.defaultFormatter": "vscode.typescript-language-features",
8 | "editor.formatOnSave": true
9 | },
10 | "editor.insertSpaces": true,
11 | "editor.rulers": [120],
12 | "editor.tabSize": 2,
13 | "editor.wordWrap": "wordWrapColumn",
14 | "editor.wordWrapColumn": 132,
15 | "files.encoding": "utf8",
16 | "files.trimTrailingWhitespace": true,
17 | "javascript.format.semicolons": "remove",
18 | "search.useIgnoreFiles": true,
19 | "typescript.locale": "en",
20 | "typescript.preferences.importModuleSpecifier": "relative",
21 | "typescript.preferences.quoteStyle": "double",
22 | "typescript.tsdk": "node_modules/typescript/lib"
23 | }
24 |
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | 0.12.0
2 | * Changed state and getters to readonly.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # direct-vuex
2 |
3 | [](https://travis-ci.com/paroi-tech/direct-vuex)
4 | [](https://david-dm.org/paroi-tech/direct-vuex)
5 | [](https://www.npmjs.com/package/direct-vuex)
6 | 
7 | [](https://github.com/paroi-tech/direct-vuex)
8 |
9 | Use and implement your Vuex store with TypeScript types. Direct-vuex doesn't require classes, therefore it is compatible with the Vue 3 composition API.
10 |
11 | ## Install
12 |
13 | First, add `direct-vuex` to a **Vue 2** application:
14 |
15 | ```sh
16 | npm install direct-vuex
17 | ```
18 |
19 | Or, in a **Vue 3** application:
20 |
21 | ```sh
22 | npm install direct-vuex@next
23 | ```
24 |
25 | ## Create the store
26 |
27 | The store can be implemented almost in the same way as usual.
28 |
29 | Create the store:
30 |
31 | ```ts
32 | import Vue from "vue"
33 | import Vuex from "vuex"
34 | import { createDirectStore } from "direct-vuex"
35 |
36 | Vue.use(Vuex)
37 |
38 | const {
39 | store,
40 | rootActionContext,
41 | moduleActionContext,
42 | rootGetterContext,
43 | moduleGetterContext
44 | } = createDirectStore({
45 | // … store implementation here …
46 | })
47 |
48 | // Export the direct-store instead of the classic Vuex store.
49 | export default store
50 |
51 | // The following exports will be used to enable types in the
52 | // implementation of actions and getters.
53 | export {
54 | rootActionContext,
55 | moduleActionContext,
56 | rootGetterContext,
57 | moduleGetterContext
58 | }
59 |
60 | // The following lines enable types in the injected store '$store'.
61 | export type AppStore = typeof store
62 | declare module "vuex" {
63 | interface Store {
64 | direct: AppStore
65 | }
66 | }
67 | ```
68 |
69 | The classic Vuex store is still accessible through the `store.original` property. We need it to initialize the Vue application:
70 |
71 | ```ts
72 | import Vue from "vue"
73 | import store from "./store"
74 |
75 | new Vue({
76 | store: store.original, // Inject the classic Vuex store.
77 | // …
78 | }).$mount("#app")
79 | ```
80 |
81 | ## Use typed wrappers from outside the store
82 |
83 | From a component, the direct store is accessible through the `direct` property of the classic store:
84 |
85 | ```ts
86 | const store = context.root.$store.direct // or: this.$store.direct
87 | ```
88 |
89 | Or, you can just import it:
90 |
91 | ```ts
92 | import store from "./store"
93 | ```
94 |
95 | Then, the old way to call an action:
96 |
97 | ```ts
98 | store.dispatch("mod1/myAction", myPayload)
99 | ```
100 |
101 | … is replaced by the following wrapper:
102 |
103 | ```ts
104 | store.dispatch.mod1.myAction(myPayload)
105 | ```
106 |
107 | … which is fully typed.
108 |
109 | Typed getters and mutations are accessible the same way:
110 |
111 | ```ts
112 | store.getters.mod1.myGetter
113 | store.commit.mod1.myMutation(myPayload)
114 | ```
115 |
116 | Notice: The underlying Vuex store can be used simultaneously if you wish, through the injected `$store` or `store.original`.
117 |
118 | ## A limitation on how to declare a State
119 |
120 | In store and module options, the `state` property shouldn't be declared with the ES6 method syntax.
121 |
122 | Valid:
123 |
124 | ```ts
125 | state: { p1: string } as Mod1State
126 | ```
127 |
128 | ```ts
129 | state: (): Mod1State => { p1: string }
130 | ```
131 |
132 | ```ts
133 | state: function (): Mod1State { return { p1: string } }
134 | ```
135 |
136 | Invalid:
137 |
138 | ```ts
139 | state(): Mod1State { return { p1: string } }
140 | ```
141 |
142 | I'm not sure why but TypeScript doesn't infer the state type correctly when we write that.
143 |
144 | ## Implement a Vuex Store with typed helpers
145 |
146 | Direct-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`.
147 |
148 | ### In a Vuex Module
149 |
150 | The 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.
151 |
152 | The 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.
153 |
154 | Here is how to use `defineModule`, `moduleActionContext` and `moduleGetterContext`:
155 |
156 | ```ts
157 | import { defineModule } from "direct-vuex"
158 | import { moduleActionContext, moduleGetterContext } from "./store"
159 |
160 | export interface Mod1State {
161 | p1: string
162 | }
163 |
164 | const mod1 = defineModule({
165 | state: (): Mod1State => {
166 | return {
167 | p1: ""
168 | }
169 | },
170 | getters: {
171 | p1OrDefault(...args): string {
172 | const { state, getters, rootState, rootGetters } = mod1GetterContext(args)
173 | // Here, 'getters', 'state', 'rootGetters' and 'rootState' are typed.
174 | // Without 'mod1GetterContext' only 'state' would be typed.
175 | return state.p1 || "default"
176 | }
177 | },
178 | mutations: {
179 | SET_P1(state, p1: string) {
180 | // Here, the type of 'state' is 'Mod1State'.
181 | state.p1 = p1
182 | }
183 | },
184 | actions: {
185 | loadP1(context, payload: { id: string }) {
186 | const { dispatch, commit, getters, state } = mod1ActionContext(context)
187 | // Here, 'dispatch', 'commit', 'getters' and 'state' are typed.
188 | }
189 | },
190 | })
191 |
192 | export default mod1
193 | const mod1GetterContext = (args: [any, any, any, any]) => moduleGetterContext(args, mod1)
194 | const mod1ActionContext = (context: any) => moduleActionContext(context, mod1)
195 | ```
196 |
197 | 2 Warnings:
198 |
199 | * 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!**
200 | * For the same reason, **declare the return type of getters each time a getter context generated by `moduleGetterContext` is used!**
201 |
202 | ### Get the typed context of a Vuex Getter, but in the root store
203 |
204 | The generated function `rootGetterContext` converts the injected action context to the direct-vuex one, at the root level (not in a module).
205 |
206 | ```ts
207 | getters: {
208 | getterInTheRootStore(...args) {
209 | const { state, getters } = rootGetterContext(args)
210 | // Here, 'getters', 'state' are typed.
211 | // Without 'rootGetterContext' only 'state' would be typed.
212 | }
213 | }
214 | ```
215 |
216 | ### Get the typed context of a Vuex Action, but in the root store
217 |
218 | The generated function `rootActionContext` converts the injected action context to the direct-vuex one, at the root level (not in a module).
219 |
220 | ```ts
221 | actions: {
222 | async actionInTheRootStore(context, payload) {
223 | const { commit, state } = rootActionContext(context)
224 | // … Here, 'commit' and 'state' are typed.
225 | }
226 | }
227 | ```
228 |
229 | ### Alternatively: Use `localGetterContext` and `localActionContext`
230 |
231 | Instead of `moduleActionContext` and `moduleGetterContext`, which imply circular dependencies, it is possible to use `localGetterContext` and `localActionContext`:
232 |
233 | ```ts
234 | import { defineModule, localActionContext, localGetterContext } from "direct-vuex"
235 |
236 | const mod1 = defineModule({
237 | // …
238 | })
239 |
240 | export default mod1
241 | const mod1GetterContext = (args: [any, any, any, any]) => localGetterContext(args, mod1)
242 | const mod1ActionContext = (context: any) => localActionContext(context, mod1)
243 | ```
244 |
245 | Now there isn't circular dependency, but getter and action contexts don't provide access to `rootState`, `rootGetters`, `rootCommit`, `rootDispatch`.
246 |
247 | Functions `localGetterContext` and `localActionContext` can be used in place of `rootGetterContext` and `rootActionContext` too.
248 |
249 | ### Use `defineGetters`
250 |
251 | The 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.
252 |
253 | ```ts
254 | import { defineGetters } from "direct-vuex"
255 | import { Mod1State } from "./mod1" // Import the local definition of the state (for example from the current module)
256 |
257 | export default defineGetters()({
258 | getter1(...args) {
259 | const { state, getters, rootState, rootGetters } = mod1GetterContext(args)
260 | // Here, 'getters', 'state', 'rootGetters' and 'rootState' are typed.
261 | // Without 'mod1GetterContext' only 'state' would be typed.
262 | },
263 | })
264 | ```
265 |
266 | Note: There is a limitation. The second parameters `getters` in a getter implementation, is not typed.
267 |
268 | ### Use `defineMutations`
269 |
270 | The 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.
271 |
272 | ```ts
273 | import { defineMutations } from "direct-vuex"
274 | import { Mod1State } from "./mod1" // Import the local definition of the state (for example from the current module)
275 |
276 | export default defineMutations()({
277 | SET_P1(state, p1: string) {
278 | // Here, the type of 'state' is 'Mod1State'.
279 | state.p1 = p1
280 | }
281 | })
282 | ```
283 |
284 | ### Use `defineActions`
285 |
286 | The 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.
287 |
288 | ```ts
289 | import { defineActions } from "direct-vuex"
290 |
291 | export default defineActions({
292 | loadP1(context, payload: { id: string }) {
293 | const { dispatch, commit, getters, state } = mod1ActionContext(context)
294 | // Here, 'dispatch', 'commit', 'getters' and 'state' are typed.
295 | }
296 | })
297 | ```
298 |
299 | ## About Direct-vuex and Circular Dependencies
300 |
301 | When 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.
302 |
303 | Here is an example of a Vuex module implementation:
304 |
305 | ```ts
306 | import { moduleActionContext } from "./store"
307 |
308 | const mod1 = {
309 | getters: {
310 | p1OrDefault(...args) {
311 | const { state, getters, rootState, rootGetters } = mod1GetterContext(args)
312 | // …
313 | }
314 | },
315 | actions: {
316 | loadP1(context, payload: { id: string }) {
317 | const { commit, rootState } = mod1ActionContext(context)
318 | // …
319 | }
320 | }
321 | }
322 |
323 | export default mod1
324 | const mod1ActionContext = (context: any) => moduleActionContext(context, mod1)
325 | const mod1GetterContext = (args: [any, any, any, any]) => moduleGetterContext(args, mod1)
326 | ```
327 |
328 | It 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.
329 |
330 | I suggest to disable the linter rule with a comment at the top of the source file.
331 |
332 | With TSLint:
333 |
334 | ```js
335 | // tslint:disable: no-use-before-declare
336 | ```
337 |
338 | With ESLint:
339 |
340 | ```js
341 | /* eslint-disable no-use-before-define */
342 | ```
343 |
344 | **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.**
345 |
346 | ## Contribute
347 |
348 | With VS Code, our recommended plugin is:
349 |
350 | * **TSLint** from Microsoft (`ms-vscode.vscode-typescript-tslint-plugin`)
351 |
--------------------------------------------------------------------------------
/build/make-bundles.js:
--------------------------------------------------------------------------------
1 | const { existsSync } = require("fs")
2 | const { writeFile, mkdir } = require("fs").promises
3 | const { join, resolve } = require("path")
4 | const { rollup } = require("rollup")
5 | const terser = require("terser")
6 |
7 | const packagePath = resolve(__dirname, "..")
8 | const distNpmPath = join(packagePath, "dist")
9 |
10 | async function build() {
11 | if (!existsSync(distNpmPath))
12 | await mkdir(distNpmPath)
13 | await makeBundle(join(__dirname, "compiled-esm", "direct-vuex.js"), "direct-vuex.esm", "esm")
14 | await makeBundle(join(__dirname, "compiled-es5", "direct-vuex.js"), "direct-vuex.umd", "umd")
15 | }
16 |
17 | async function makeBundle(mainFile, bundleName, format) {
18 | const bundle = await rollup({
19 | input: mainFile,
20 | context: "this" // preserve 'this' in TS's ES5 helpers
21 | })
22 | const { output } = await bundle.generate({
23 | format,
24 | name: "DirectVuex",
25 | sourcemap: false,
26 | exports: "named",
27 | globals: {
28 | vuex: "Vuex" // global variable name for UMD and System
29 | },
30 | })
31 | const bundleCode = output[0].code
32 | const minified = terser.minify({
33 | bundle: bundleCode
34 | })
35 | if (minified.error)
36 | throw minified.error
37 |
38 | await writeFile(join(distNpmPath, `${bundleName}.min.js`), minified.code)
39 | await writeFile(join(distNpmPath, `${bundleName}.js`), bundleCode)
40 | }
41 |
42 | build().catch(err => console.log(err.message, err.stack))
43 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | testEnvironment: "node",
4 | transform: {
5 | "^.+\\.ts$": "ts-jest",
6 | },
7 | moduleFileExtensions: [
8 | "js",
9 | "json",
10 | "ts",
11 | ],
12 | testMatch: [
13 | "**/(src|tests)/**/*.spec.(js|ts)",
14 | ],
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "direct-vuex",
3 | "version": "1.0.0-rc3",
4 | "description": "Use and implement your Vuex store with TypeScript types. Compatible with the Vue 3 composition API.",
5 | "author": "Paleo",
6 | "files": [
7 | "dist",
8 | "types"
9 | ],
10 | "main": "dist/direct-vuex.umd.js",
11 | "module": "dist/direct-vuex.esm.js",
12 | "types": "types/index.d.ts",
13 | "scripts": {
14 | "prepublishOnly": "npm run test",
15 | "prepare": "npm run build",
16 | "clear": "rimraf 'build/compiled-*/*'",
17 | "tsc": "tsc",
18 | "tsc-es5": "tsc --target ES5 --outDir build/compiled-es5",
19 | "tsc:watch": "tsc --watch",
20 | "make-bundles": "node build/make-bundles.js",
21 | "build": "npm run clear && npm run tsc && npm run tsc-es5 && npm run make-bundles",
22 | "lint": "tslint -p tsconfig.json -t verbose",
23 | "test": "jest",
24 | "test:watch": "jest --watch"
25 | },
26 | "peerDependencies": {
27 | "vue": "3",
28 | "vuex": "4"
29 | },
30 | "devDependencies": {
31 | "@types/jest": "^26.0.20",
32 | "jest": "^26.6.3",
33 | "rimraf": "^3.0.2",
34 | "rollup": "^2.39.1",
35 | "terser": "^5.6.0",
36 | "ts-jest": "^26.5.2",
37 | "tslint": "^6.1.3",
38 | "typescript": "^4.2.2",
39 | "vue": "^3.0.5",
40 | "vuex": "^4.0.0"
41 | },
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/paroi-tech/direct-vuex.git"
45 | },
46 | "license": "CC0-1.0",
47 | "keywords": [
48 | "vuex",
49 | "typescript"
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/src/direct-vuex.ts:
--------------------------------------------------------------------------------
1 | import { ActionContext, createStore, Store } from "vuex"
2 | import { ActionsImpl, GettersImpl, ModuleOptions, ModulesImpl, MutationsImpl, StateOf, StoreOptions, StoreOrModuleOptions, WithOptionalState } from "../types"
3 | import { CreatedStore, ToDirectStore, VuexStore } from "../types/direct-types"
4 |
5 | export function createDirectStore<
6 | O extends WithOptionalState,
7 | S = StateOf
8 | >(options: O & StoreOptions): CreatedStore {
9 | const original = createStore(options as any) as VuexStore
10 |
11 | const store: ToDirectStore = {
12 | get state() {
13 | return original.state as any
14 | },
15 | getters: toDirectGetters(options, original.getters),
16 | commit: toDirectCommit(options, original.commit),
17 | dispatch: toDirectDispatch(options, original.dispatch),
18 | original
19 | }
20 |
21 | original.direct = store
22 |
23 | return {
24 | store,
25 | rootGetterContext:
26 | ([state, getters]: [any, any]) => getModuleGetterContext([state, getters, state, getters], options, options),
27 | moduleGetterContext:
28 | (args: [any, any, any, any], moduleOptions: any) =>
29 | getModuleGetterContext(args, moduleOptions, options),
30 | rootActionContext: (originalContext: any) => getModuleActionContext(originalContext, options, options),
31 | moduleActionContext:
32 | (originalContext: any, moduleOptions: any) => getModuleActionContext(originalContext, moduleOptions, options),
33 | }
34 | }
35 |
36 | export function localGetterContext(
37 | [state, getters]: [any, any, ...any[]], options: O
38 | ): any {
39 | return getModuleGetterContext([state, getters, undefined, undefined], options)
40 | }
41 |
42 | export function localActionContext(
43 | originalContext: ActionContext,
44 | options: O
45 | ): any {
46 | return getModuleActionContext(originalContext, options)
47 | }
48 |
49 | export function defineModule<
50 | O extends WithOptionalState,
51 | S = StateOf
52 | >(options: O & ModuleOptions): O {
53 | return options
54 | }
55 |
56 | export function defineModules(): ((modules: T & ModulesImpl) => T) {
57 | return modules => modules
58 | }
59 |
60 | export function defineGetters(): ((getters: T & GettersImpl) => T) {
61 | return getters => getters
62 | }
63 |
64 | export function defineMutations(): ((mutations: T & MutationsImpl) => T) {
65 | return mutations => mutations
66 | }
67 |
68 | export function defineActions(actions: T & ActionsImpl): T {
69 | return actions
70 | }
71 |
72 | export const createModule = obsolete(defineModule, "createModule", "defineModule")
73 | export const createModules = obsolete(defineModules, "createModules", "defineModules")
74 | export const createGetters = obsolete(defineGetters, "createGetters", "defineGetters")
75 | export const createMutations = obsolete(defineMutations, "createMutations", "defineMutations")
76 | export const createActions = obsolete(defineActions, "createActions", "defineActions")
77 |
78 | function obsolete any>(fn: T, oldName: string, newName: string): T {
79 | return ((...args) => {
80 | // tslint:disable-next-line:no-console
81 | console.warn(`Function '${oldName}' is obsolete, please use '${newName}'.`)
82 | return fn(...args)
83 | }) as T
84 | }
85 |
86 | export default {
87 | createDirectStore, defineModule, defineModules, defineGetters, defineMutations, defineActions,
88 | localGetterContext, localActionContext,
89 | createModule, createModules, createGetters, createMutations, createActions
90 | }
91 |
92 | // Getters
93 |
94 | const gettersCache = new WeakMap["getters"], any>()
95 |
96 | function toDirectGetters(options: StoreOrModuleOptions, originalGetters: Store["getters"]) {
97 | let getters = gettersCache.get(originalGetters)
98 | // console.log(">> to-getters", getters ? "FROM_CACHE" : "CREATE", options)
99 | if (!getters) {
100 | getters = gettersFromOptions({}, options, originalGetters)
101 | gettersCache.set(originalGetters, getters)
102 | }
103 | return getters
104 | }
105 |
106 | function gettersFromOptions(
107 | result: any,
108 | options: StoreOrModuleOptions,
109 | originalGetters: Store["getters"],
110 | hierarchy: string[] = []
111 | ): any {
112 | if (options.getters)
113 | createDirectGetters(result, options.getters, originalGetters, hierarchy)
114 | if (options.modules) {
115 | for (const moduleName of Object.keys(options.modules)) {
116 | const moduleOptions = options.modules[moduleName]
117 | if (moduleOptions.namespaced)
118 | result[moduleName] = gettersFromOptions({}, moduleOptions, originalGetters, [...hierarchy, moduleName])
119 | else
120 | gettersFromOptions(result, moduleOptions, originalGetters, hierarchy)
121 | }
122 | }
123 | return result
124 | }
125 |
126 | function createDirectGetters(
127 | result: any,
128 | gettersImpl: GettersImpl,
129 | originalGetters: Store["getters"],
130 | hierarchy?: string[]
131 | ) {
132 | const prefix = !hierarchy || hierarchy.length === 0 ? "" : `${hierarchy.join("/")}/`
133 | for (const name of Object.keys(gettersImpl)) {
134 | Object.defineProperties(result, {
135 | [name]: {
136 | get: () => originalGetters[`${prefix}${name}`]
137 | }
138 | })
139 | }
140 | }
141 |
142 | // Mutations
143 |
144 | const commitCache = new WeakMap["commit"], any>()
145 |
146 | function toDirectCommit(options: StoreOrModuleOptions, originalCommit: Store["commit"]) {
147 | let commit = commitCache.get(originalCommit)
148 | // console.log(">> to-commit", commit ? "FROM_CACHE" : "CREATE", options)
149 | if (!commit) {
150 | commit = commitFromOptions({}, options, originalCommit)
151 | commitCache.set(originalCommit, commit)
152 | }
153 | return commit
154 | }
155 |
156 | const rootCommitCache = new WeakMap["commit"], any>()
157 |
158 | function toDirectRootCommit(rootOptions: StoreOptions, originalCommit: Store["commit"]) {
159 | let commit = rootCommitCache.get(originalCommit)
160 | // console.log(">> to-rootCommit", commit ? "FROM_CACHE" : "CREATE", rootOptions)
161 | if (!commit) {
162 | const origCall = (mutation: string, payload: any) => originalCommit(mutation, payload, { root: true })
163 | commit = commitFromOptions({}, rootOptions, origCall)
164 | rootCommitCache.set(originalCommit, commit)
165 | }
166 | return commit
167 | }
168 |
169 | function commitFromOptions(
170 | result: any,
171 | options: StoreOrModuleOptions,
172 | originalCommitCall: (mutation: string, payload: any) => void,
173 | hierarchy: string[] = []
174 | ): any {
175 | if (options.mutations)
176 | createDirectMutations(result, options.mutations, originalCommitCall, hierarchy)
177 | if (options.modules) {
178 | for (const moduleName of Object.keys(options.modules)) {
179 | const moduleOptions = options.modules[moduleName]
180 | if (moduleOptions.namespaced)
181 | result[moduleName] = commitFromOptions({}, moduleOptions, originalCommitCall, [...hierarchy, moduleName])
182 | else
183 | commitFromOptions(result, moduleOptions, originalCommitCall, hierarchy)
184 | }
185 | }
186 | return result
187 | }
188 |
189 | function createDirectMutations(
190 | result: any,
191 | mutationsImpl: MutationsImpl,
192 | originalCommitCall: (mutation: string, payload: any) => void,
193 | hierarchy?: string[]
194 | ) {
195 | const prefix = !hierarchy || hierarchy.length === 0 ? "" : `${hierarchy.join("/")}/`
196 | for (const name of Object.keys(mutationsImpl))
197 | result[name] = (payload: any) => originalCommitCall(`${prefix}${name}`, payload)
198 | }
199 |
200 | // Actions
201 |
202 | const dispatchCache = new WeakMap["dispatch"], any>()
203 |
204 | function toDirectDispatch(options: StoreOrModuleOptions, originalDispatch: Store["dispatch"]) {
205 | let dispatch = dispatchCache.get(originalDispatch)
206 | // console.log(">> to-dispatch", dispatch ? "FROM_CACHE" : "CREATE", options)
207 | if (!dispatch) {
208 | dispatch = dispatchFromOptions({}, options, originalDispatch)
209 | dispatchCache.set(originalDispatch, dispatch)
210 | }
211 | return dispatch
212 | }
213 |
214 | const rootDispatchCache = new WeakMap["dispatch"], any>()
215 |
216 | function toDirectRootDispatch(rootOptions: StoreOptions, originalDispatch: Store["dispatch"]) {
217 | let dispatch = rootDispatchCache.get(originalDispatch)
218 | // console.log(">> to-rootDispatch", dispatch ? "FROM_CACHE" : "CREATE", rootOptions)
219 | if (!dispatch) {
220 | const origCall = (mutation: string, payload: any) => originalDispatch(mutation, payload, { root: true })
221 | dispatch = dispatchFromOptions({}, rootOptions, origCall)
222 | rootDispatchCache.set(originalDispatch, dispatch)
223 | }
224 | return dispatch
225 | }
226 |
227 | function dispatchFromOptions(
228 | result: any,
229 | options: StoreOrModuleOptions,
230 | originalDispatchCall: (action: string, payload: any) => any,
231 | hierarchy: string[] = []
232 | ): any {
233 | if (options.actions)
234 | createDirectActions(result, options.actions, originalDispatchCall, hierarchy)
235 | if (options.modules) {
236 | for (const moduleName of Object.keys(options.modules)) {
237 | const moduleOptions = options.modules[moduleName]
238 | if (moduleOptions.namespaced)
239 | result[moduleName] = dispatchFromOptions({}, moduleOptions, originalDispatchCall, [...hierarchy, moduleName])
240 | else
241 | dispatchFromOptions(result, moduleOptions, originalDispatchCall, hierarchy)
242 | }
243 | }
244 | return result
245 | }
246 |
247 | function createDirectActions(
248 | result: any,
249 | actionsImpl: ActionsImpl,
250 | originalDispatchCall: (action: string, payload: any) => any,
251 | hierarchy?: string[]
252 | ) {
253 | const prefix = !hierarchy || hierarchy.length === 0 ? "" : `${hierarchy.join("/")}/`
254 | for (const name of Object.keys(actionsImpl))
255 | result[name] = (payload?: any) => originalDispatchCall(`${prefix}${name}`, payload)
256 | }
257 |
258 | // GetterContext
259 |
260 | const getterContextCache = new WeakMap()
261 |
262 | function getModuleGetterContext(args: [any, any, any, any], options: ModuleOptions, rootOptions?: StoreOptions) {
263 | const [state, getters, rootState, rootGetters] = args
264 | let context = actionContextCache.get(state)
265 | // console.log(">> to-getterContext", context ? "FROM_CACHE" : "CREATE", options)
266 | if (!context) {
267 | if (rootOptions) {
268 | context = {
269 | get rootState() {
270 | return rootState
271 | },
272 | get rootGetters() {
273 | return toDirectGetters(rootOptions!, rootGetters)
274 | },
275 | get state() {
276 | return state
277 | },
278 | get getters() {
279 | return toDirectGetters(options, getters)
280 | }
281 | }
282 | } else {
283 | context = {
284 | get state() {
285 | return state
286 | },
287 | get getters() {
288 | return toDirectGetters(options, getters)
289 | }
290 | }
291 | }
292 | if (state) // Can be undefined in unit tests
293 | getterContextCache.set(state, context)
294 | }
295 |
296 | return context
297 | }
298 |
299 | // ActionContext
300 |
301 | const actionContextCache = new WeakMap()
302 |
303 | function getModuleActionContext(
304 | originalContext: ActionContext,
305 | options: ModuleOptions,
306 | rootOptions?: StoreOptions
307 | ): any {
308 | let context = actionContextCache.get(originalContext.state)
309 | // console.log(">> to-actionContext", context ? "FROM_CACHE" : "CREATE", options)
310 | if (!context) {
311 | if (rootOptions) {
312 | context = {
313 | get rootState() {
314 | return originalContext.rootState
315 | },
316 | get rootGetters() {
317 | return toDirectGetters(rootOptions!, originalContext.rootGetters)
318 | },
319 | get rootCommit() {
320 | return toDirectRootCommit(rootOptions!, originalContext.commit)
321 | },
322 | get rootDispatch() {
323 | return toDirectRootDispatch(rootOptions!, originalContext.dispatch)
324 | },
325 | get state() {
326 | return originalContext.state
327 | },
328 | get getters() {
329 | return toDirectGetters(options, originalContext.getters)
330 | },
331 | get commit() {
332 | return toDirectCommit(options, originalContext.commit)
333 | },
334 | get dispatch() {
335 | return toDirectDispatch(options, originalContext.dispatch)
336 | }
337 | }
338 | } else {
339 | context = {
340 | get state() {
341 | return originalContext.state
342 | },
343 | get getters() {
344 | return toDirectGetters(options, originalContext.getters)
345 | },
346 | get commit() {
347 | return toDirectCommit(options, originalContext.commit)
348 | },
349 | get dispatch() {
350 | return toDirectDispatch(options, originalContext.dispatch)
351 | }
352 | }
353 | }
354 | if (originalContext.state) // Can be undefined in unit tests
355 | actionContextCache.set(originalContext.state, context)
356 | }
357 | return context
358 | }
359 |
--------------------------------------------------------------------------------
/tests/action-context.spec.ts:
--------------------------------------------------------------------------------
1 | import { createDirectStore, defineModule } from "../src/direct-vuex"
2 |
3 | describe("Action Contexts", () => {
4 |
5 | test("Use 'dispatch' and 'rootDispatch' from action implementation", async () => {
6 | const mod1 = defineModule({
7 | namespaced: true,
8 | actions: {
9 | async a2(context: any, payload: { p2: number }) {
10 | const { dispatch, rootDispatch } = mod1ActionContext(context)
11 |
12 | const p3: number = await dispatch.a3({ p3: 123 })
13 | expect(p3).toBe(123)
14 |
15 | const p3bis: number = await rootDispatch.mod1.a3({ p3: 123 })
16 | expect(p3bis).toBe(123)
17 | },
18 | async a3(context: any, payload: { p3: number }) {
19 | return payload.p3
20 | }
21 | }
22 | })
23 | const mod1ActionContext = (context: any) => moduleActionContext(context, mod1)
24 |
25 | const { store, rootActionContext, moduleActionContext } = createDirectStore({
26 | actions: {
27 | async a1(context: any, payload: { p1: string }) {
28 | const { dispatch, rootDispatch } = rootActionContext(context)
29 |
30 | expect(dispatch.a1).toBeDefined()
31 | expect(rootDispatch.a1).toBeDefined()
32 |
33 | return payload.p1
34 | }
35 | },
36 | modules: {
37 | mod1
38 | }
39 | })
40 |
41 | await store.dispatch.a1({ p1: "abc" })
42 | await store.dispatch.mod1.a2({ p2: 123 })
43 | })
44 | })
--------------------------------------------------------------------------------
/tests/namespaced.spec.ts:
--------------------------------------------------------------------------------
1 | import { createDirectStore } from "../src/direct-vuex"
2 |
3 | describe("Namespaced Modules", () => {
4 |
5 | test("Access to namespaced action", async () => {
6 | const { store } = createDirectStore({
7 | actions: {
8 | a1: async (context, payload: { p1: string }) => payload.p1
9 | },
10 | modules: {
11 | mod1: {
12 | namespaced: true,
13 | actions: {
14 | a2: async (context, payload: { p2: number }) => payload.p2
15 | }
16 | }
17 | }
18 | })
19 |
20 | const p1: string = await store.dispatch.a1({ p1: "abc" })
21 | expect(p1).toBe("abc")
22 |
23 | const p2: number = await store.dispatch.mod1.a2({ p2: 123 })
24 | expect(p2).toBe(123)
25 | })
26 |
27 | test("Access to namespaced mutation", async () => {
28 | const { store } = createDirectStore({
29 | mutations: {
30 | mu1: (state, payload: { p1: string }) => { }
31 | },
32 | modules: {
33 | mod1: {
34 | namespaced: true,
35 | mutations: {
36 | mu2: (state, payload: { p2: number }) => { }
37 | }
38 | }
39 | }
40 | })
41 |
42 | store.commit.mu1({ p1: "abc" })
43 | store.commit.mod1.mu2({ p2: 123 })
44 | })
45 |
46 | test("Access to namespaced getter", async () => {
47 | const { store } = createDirectStore({
48 | getters: {
49 | g1: (state) => "abc"
50 | },
51 | modules: {
52 | mod1: {
53 | namespaced: true,
54 | getters: {
55 | g2: (state: any) => 123
56 | }
57 | }
58 | }
59 | })
60 |
61 | const g1: string = store.getters.g1
62 | expect(g1).toBe("abc")
63 | const g2: number = store.getters.mod1.g2
64 | expect(g2).toBe(123)
65 | })
66 |
67 | test("Access to namespaced getter with parameter", async () => {
68 | const { store } = createDirectStore({
69 | getters: {
70 | hello: state => (name: string) => `Hello, ${name}!`
71 | },
72 | })
73 |
74 | const g1: string = store.getters.hello("John")
75 | expect(g1).toBe("Hello, John!")
76 | })
77 | })
--------------------------------------------------------------------------------
/tests/non-namespaced.spec.ts:
--------------------------------------------------------------------------------
1 | import { createDirectStore } from "../src/direct-vuex"
2 |
3 | describe("Non-Namespaced Modules", () => {
4 |
5 | test("Merge module action in the root store", async () => {
6 | const { store } = createDirectStore({
7 | actions: {
8 | a1: async (context: any, payload: { p1: string }) => payload.p1
9 | },
10 | modules: {
11 | mod1: {
12 | namespaced: false,
13 | actions: {
14 | a2: async (context: any, payload: { p2: number }) => payload.p2
15 | }
16 | }
17 | }
18 | })
19 |
20 | const p1: string = await store.dispatch.a1({ p1: "abc" })
21 | expect(p1).toBe("abc")
22 |
23 | const p2: number = await store.dispatch.a2({ p2: 123 })
24 | expect(p2).toBe(123)
25 | })
26 |
27 | test("Merge module action: omit the 'namespaced' property", async () => {
28 | const { store } = createDirectStore({
29 | actions: {
30 | a1: async (context: any, payload: { p1: string }) => payload.p1
31 | },
32 | modules: {
33 | mod1: {
34 | actions: {
35 | a2: async (context: any, payload: { p2: number }) => payload.p2
36 | }
37 | }
38 | }
39 | })
40 |
41 | const p1: string = await store.dispatch.a1({ p1: "abc" })
42 | expect(p1).toBe("abc")
43 |
44 | const p2: number = await store.dispatch.a2({ p2: 123 })
45 | expect(p2).toBe(123)
46 | })
47 |
48 | test("Merge module mutation in the root store", async () => {
49 | const { store } = createDirectStore({
50 | mutations: {
51 | mu1: (state: any, payload: { p1: string }) => { }
52 | },
53 | modules: {
54 | mod1: {
55 | namespaced: false,
56 | mutations: {
57 | mu2: (state: any, payload: { p2: number }) => { }
58 | }
59 | }
60 | }
61 | })
62 |
63 | store.commit.mu1({ p1: "abc" })
64 | store.commit.mu2({ p2: 123 })
65 | })
66 |
67 | test("Merge module getter in the root store", async () => {
68 | const { store } = createDirectStore({
69 | getters: {
70 | g1: (state: any) => "abc"
71 | },
72 | modules: {
73 | mod1: {
74 | namespaced: false,
75 | getters: {
76 | g2: (state: any) => 123
77 | }
78 | }
79 | }
80 | })
81 |
82 | const g1: string = store.getters.g1
83 | expect(g1).toBe("abc")
84 | const g2: number = store.getters.g2
85 | expect(g2).toBe(123)
86 | })
87 | })
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ES2015",
5 | "module": "ES2015",
6 | "sourceMap": false,
7 | "lib": ["es2020", "DOM"],
8 | "moduleResolution": "node",
9 | "esModuleInterop": true,
10 | "outDir": "build/compiled-esm",
11 | "noEmitOnError": true,
12 | "declaration": false
13 | },
14 | "include": [
15 | "src",
16 | "types"
17 | ],
18 | "exclude": [
19 | "node_modules",
20 | "dist",
21 | "dist-esm"
22 | ]
23 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warning",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "linterOptions": {
7 | "exclude": [
8 | "node_modules/**"
9 | ]
10 | },
11 | "rules": {
12 | "arrow-parens": false,
13 | "curly": false,
14 | "eofline": false,
15 | "indent": [
16 | true,
17 | "spaces",
18 | 2
19 | ],
20 | "interface-name": false,
21 | "max-classes-per-file": false,
22 | "max-line-length": false,
23 | "member-access": [
24 | true,
25 | "no-public"
26 | ],
27 | "no-conditional-assignment": false,
28 | "no-consecutive-blank-lines": false,
29 | "no-empty": [
30 | true,
31 | "allow-empty-catch",
32 | "allow-empty-functions"
33 | ],
34 | "no-shadowed-variable": false,
35 | "no-string-literal": false,
36 | "no-var-requires": false,
37 | "ordered-imports": true,
38 | "object-literal-sort-keys": false,
39 | "object-literal-key-quotes": false,
40 | "only-arrow-functions": false,
41 | "prefer-const": true,
42 | "quotemark": [
43 | true,
44 | "double"
45 | ],
46 | "semicolon": [
47 | true,
48 | "never"
49 | ],
50 | "space-before-function-paren": [
51 | true,
52 | {
53 | "anonymous": "always",
54 | "named": "never",
55 | "asyncArrow": "always"
56 | }
57 | ],
58 | "trailing-comma": [
59 | true,
60 | {
61 | "singleline": "never"
62 | }
63 | ],
64 | "variable-name": [
65 | true,
66 | "allow-leading-underscore",
67 | "allow-pascal-case"
68 | ]
69 | }
70 | }
--------------------------------------------------------------------------------
/types/direct-types.d.ts:
--------------------------------------------------------------------------------
1 | import { ActionContext, Store } from "vuex"
2 | import { ActionsImpl, GettersImpl, ModuleOptions, ModulesImpl, MutationsImpl, StoreOptions, StoreOrModuleOptions } from "./index"
3 |
4 | export interface CreatedStore {
5 | store: ToDirectStore
6 |
7 | rootGetterContext(args: [any, any]): DirectGetterContext
8 | moduleGetterContext(
9 | args: [any, any, any, any], module: O
10 | ): DirectGetterContext
11 |
12 | rootActionContext(originalContext: ActionContext): DirectActionContext
13 | moduleActionContext(
14 | originalContext: ActionContext,
15 | module: O
16 | ): DirectActionContext
17 | }
18 |
19 | export type ToDirectStore = ShowContent<{
20 | readonly state: ShowContent>
21 | getters: ShowContent>
22 | commit: ShowContent>
23 | dispatch: ShowContent>
24 | original: VuexStore
25 | }>
26 |
27 | export type VuexStore = Store>> & {
28 | direct: ToDirectStore
29 | }
30 |
31 | // State
32 |
33 | type DirectState =
34 | ToStateObj
35 | & GetStateInModules>
36 |
37 | type GetStateInModules = {
38 | readonly [M in keyof I]: DirectState
39 | }
40 |
41 | type ToStateObj = T extends (() => any) ? Readonly> : Readonly
42 |
43 | // Getters
44 |
45 | // export type SelfGetters = ToDirectGetters>
46 |
47 | type DirectGetters =
48 | ToDirectGetters>
49 | & GetGettersInModules>>
50 | & MergeGettersFromModules>>
51 |
52 | type GetGettersInModules = {
53 | readonly [M in keyof I]: DirectGetters
54 | }
55 |
56 | type ToDirectGetters = {
57 | readonly [K in keyof T]: ReadonlyReturnTypeExceptCb
58 | }
59 |
60 | type ReadonlyReturnTypeExceptCb any> =
61 | T extends ((...args1: any) => (...args2: any) => any)
62 | ? ReturnType
63 | : Readonly>
64 |
65 | type MergeGettersFromModules =
66 | UnionToIntersection>>
67 |
68 | // Mutations
69 |
70 | type DirectMutations =
71 | ToDirectMutations>
72 | & GetMutationsInModules>>
73 | & MergeMutationsFromModules>>
74 |
75 | type GetMutationsInModules = {
76 | [M in keyof I]: DirectMutations
77 | }
78 |
79 | type ToDirectMutations = {
80 | [K in keyof T]: Parameters[1] extends undefined
81 | ? (() => void)
82 | : (Extract[1], undefined> extends never ?
83 | ((payload: Parameters[1]) => void) :
84 | ((payload?: Parameters[1]) => void))
85 | }
86 |
87 | type MergeMutationsFromModules =
88 | UnionToIntersection>>
89 |
90 | // Actions
91 |
92 | type DirectActions =
93 | ToDirectActions>
94 | & GetActionsInModules>>
95 | & MergeActionsFromModules>>
96 |
97 | type GetActionsInModules = {
98 | [M in keyof I]: DirectActions
99 | }
100 |
101 | type ToDirectActions = {
102 | [K in keyof T]: Parameters[1] extends undefined
103 | ? (() => PromiseOf>)
104 | : (Extract[1], undefined> extends never ?
105 | ((payload: Parameters[1]) => PromiseOf>) :
106 | ((payload?: Parameters[1]) => PromiseOf>))
107 | }
108 |
109 | type MergeActionsFromModules =
110 | UnionToIntersection>>
111 |
112 | // ActionContext
113 |
114 | export type DirectActionContext = ShowContent<{
115 | rootState: DirectState
116 | rootGetters: DirectGetters
117 | rootCommit: DirectMutations
118 | rootDispatch: DirectActions
119 | state: DirectState
120 | getters: DirectGetters
121 | commit: DirectMutations
122 | dispatch: DirectActions
123 | }>
124 |
125 | export type DirectGetterContext = ShowContent<{
126 | rootState: DirectState
127 | rootGetters: DirectGetters
128 | state: DirectState
129 | getters: DirectGetters
130 | }>
131 |
132 | // Common helpers
133 |
134 | type PromiseOf = T extends Promise ? T : Promise
135 |
136 | type FilterNamespaced = Pick>
137 | type KeyOfType = { [P in keyof T]: T[P] extends U ? P : never }[keyof T]
138 |
139 | type FilterNotNamespaced = Pick>
140 | type NotKeyOfType = { [P in keyof T]: T[P] extends U ? never : P }[keyof T]
141 |
142 | type OrEmpty = T extends {} ? T : {}
143 |
144 | type UnionToIntersection =
145 | (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
146 |
147 | type ShowContent =
148 | T extends Function ? T :
149 | T extends object ?
150 | T extends infer O ? { [K in keyof O]: ShowContentDepth1 } : never
151 | : T
152 |
153 | type ShowContentDepth1 =
154 | T extends Function ? T :
155 | T extends object ?
156 | T extends infer O ? { [K in keyof O]: O[K] } : never
157 | : T
158 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { ActionContext } from "vuex"
2 | import { CreatedStore, DirectActionContext, DirectGetterContext } from "./direct-types"
3 |
4 | export function createDirectStore<
5 | O extends WithOptionalState,
6 | S = StateOf
7 | >(options: O & StoreOptions): CreatedStore
8 |
9 | export function defineModule<
10 | O extends WithOptionalState,
11 | S = StateOf
12 | >(options: O & ModuleOptions): O
13 |
14 | export function defineModules(): ((modules: T & ModulesImpl) => T)
15 | export function defineGetters(): ((getters: T & GettersImpl) => T)
16 | export function defineMutations(): ((mutations: T & MutationsImpl) => T)
17 | export function defineActions(actions: T & ActionsImpl): T
18 |
19 | export function localGetterContext(
20 | args: [any, any, ...any[]], options: O
21 | ): DirectGetterContext
22 |
23 | export function localActionContext(
24 | originalContext: ActionContext,
25 | options: O
26 | ): DirectActionContext
27 |
28 | /**
29 | * @deprecated Use `defineModule`.
30 | */
31 | export function createModule<
32 | O extends WithOptionalState,
33 | S = StateOf
34 | >(options: O & ModuleOptions): O
35 |
36 | /**
37 | * @deprecated Use `defineModules`.
38 | */
39 | export function createModules(): ((modules: T & ModulesImpl) => T)
40 | /**
41 | * @deprecated Use `defineGetters`.
42 | */
43 | export function createGetters(): ((getters: T & GettersImpl) => T)
44 | /**
45 | * @deprecated Use `defineMutations`.
46 | */
47 | export function createMutations(): ((mutations: T & MutationsImpl) => T)
48 | /**
49 | * @deprecated Use `defineActions`.
50 | */
51 | export function createActions(actions: T & ActionsImpl): T
52 |
53 |
54 | export type WithOptionalState = { state: StateDeclaration } | {}
55 | export type StateOf = O extends { state: StateDeclaration } ? O["state"] : {}
56 | type StateDeclaration = any | (() => any)
57 |
58 | /*
59 | * Types for Vuex Store Options
60 | */
61 |
62 | export interface StoreOrModuleOptions {
63 | state?: S extends object ? ((() => S) | S) : never,
64 | getters?: GettersImpl
65 | mutations?: MutationsImpl
66 | actions?: ActionsImpl
67 | modules?: ModulesImpl
68 | plugins?: PluginImpl[]
69 | }
70 |
71 | export interface StoreOptions extends StoreOrModuleOptions {
72 | strict?: boolean
73 | }
74 |
75 | export interface ModuleOptions extends StoreOrModuleOptions {
76 | namespaced?: boolean
77 | }
78 |
79 | export interface ModulesImpl { [moduleName: string]: ModuleOptions }
80 |
81 | export interface GettersImpl {
82 | [name: string]: GetterImpl
83 | }
84 |
85 | export type GetterImpl = (state: S, getters: G, rootState: any, rootGetters: any) => any
86 |
87 | export interface MutationsImpl {
88 | [name: string]: MutationImpl
89 | }
90 |
91 | export type MutationImpl = (state: S, payload: any) => void
92 |
93 | export interface ActionsImpl {
94 | [name: string]: ActionImpl
95 | }
96 |
97 | export type ActionImpl = (context: ActionContext, payload: any) => any
98 |
99 | export type PluginImpl = (store: any) => any
--------------------------------------------------------------------------------