├── .github
├── FUNDING.yml
└── stale.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.EN.md
├── README.md
├── babel.config.js
├── demo
├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.js
│ ├── router.js
│ └── views
│ │ ├── Home.vue
│ │ └── More.vue
├── vue.config.js
└── yarn.lock
├── jest.config.js
├── logo.png
├── package.json
├── scripts
└── publish-pages.sh
├── src
├── adapters
│ └── SessionStorage.ts
├── db.ts
├── main.ts
└── types
│ └── global.d.ts
├── test
├── db.test.ts
└── main.test.ts
├── tsconfig.json
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [boenfu]
4 | patreon: boen
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | custom: # Replace with a single custom sponsorship URL
9 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Installable packages
2 |
3 | node_modules/
4 |
5 | # Configuration
6 |
7 | .config/
8 | .vscode/
9 |
10 | # Building artifacts
11 | lib/
12 | bld/
13 | .cache/
14 | *.tgz
15 |
16 | # Debugging outputs
17 |
18 | *.log
19 |
20 | # Testing files
21 |
22 | /test.*
23 |
24 | # GitHub Pages
25 |
26 | /gh-pages
27 |
28 | */dist/
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 | - "11"
5 | cache:
6 | directory:
7 | - $HOME/.yarn/bin
8 | before_install:
9 | - curl -o- -L https://yarnpkg.com/install.sh | bash
10 | - export PATH="$HOME/.yarn/bin:$PATH"
11 | before_script:
12 | - yarn test
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 BoenFu
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.EN.md:
--------------------------------------------------------------------------------
1 | English | [简体中文](./README.md)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | vuex-along - A plugins for vuex
14 | Automatically restore vuex state after refreshing the page
15 |
16 | # Directory
17 |
18 | - [Install](#Install)
19 | - [Usage](#Usage)
20 | - [Example](#Example)
21 | - [Options](#Options)
22 | - [Cleanup](#Cleanup)
23 | - [RunDemo](#RunDemo)
24 | - [Tips](#Tips)
25 | - [Contribution](#Contribution)
26 | - [Maintainers](#Maintainers)
27 | - [License](#license)
28 |
29 | ## Install
30 |
31 | ```shell
32 | npm install vuex-along --save
33 | # or
34 | yarn add vuex-along
35 | ```
36 |
37 | ## Usage
38 |
39 | ```javascript
40 | import createVuexAlong from 'vuex-along'
41 |
42 | export default new Vuex.Store({
43 | state:{...},
44 | modules:{...},
45 | plugins: [createVuexAlong()]
46 | });
47 | ```
48 |
49 | > Now, the plugin has been in effect, and all state is stored to localStorage by default.
50 | >
51 | > Set [Options](#Options) to meet the usage requirements
52 | >
53 | >- [secure-adapter](https://github.com/boenfu/vuex-along-secure-adapter)
54 |
55 | ## Example
56 |
57 | [→ Online Example](https://boenfu.github.io/vuex-along/)
58 |
59 | ```javascript
60 | import createVuexAlong from "vuex-along";
61 |
62 | const moduleA = {
63 | state: {
64 | a1: "hello",
65 | a2: "world",
66 | },
67 | };
68 |
69 | const store = new Vuex.Store({
70 | state: {
71 | count: 0,
72 | },
73 | modules: {
74 | ma: moduleA,
75 | },
76 | plugins: [
77 | createVuexAlong({
78 | //Set the collection name to avoid multi-project data conflicts with the same site
79 | name: "hello-vuex-along",
80 | local: {
81 | list: ["ma"],
82 | // Filter the module ma data, save the other to localStorage
83 | isFilter: true,
84 | },
85 | session: {
86 | // Save a1 in module ma to sessionStorage
87 | list: ["ma.a1"],
88 | },
89 | }),
90 | ],
91 | });
92 | ```
93 |
94 | ## Options
95 |
96 | #### VuexAlongOptions
97 |
98 | | **Field** | **Required** | **Type** | **Description** |
99 | | ----------- | ------------ | -------- | ------------------------------------------------------------------------ |
100 | | name | no | String | Set the name of the local data collection, the default is **vuex-along** |
101 | | local | no | Object | LocalStorage configuration, see #WatchOptions |
102 | | session | no | Object | SessionStorage configuration, see #WatchOptions |
103 | | justSession | no | Boolean | Use only sessionStorage |
104 |
105 | #### WatchOptions
106 |
107 | | **Field** | **Required** | **Type** | **Description** |
108 | | --------- | ------------ | --------- | ------------------------------------------------------------------------------ |
109 | | list | yes | String [] | List of strings of attribute names or module names that need to be listened to |
110 | | isFilter | no | Boolean | Filter fields in list instead of saving |
111 |
112 | ## Cleanup
113 |
114 | ```typescript
115 | window.clearVuexAlong(local = true, session = true):void;
116 | clearVuexAlong() // localStorage and sessionStorage will be cleaned up
117 | clearVuexAlong(true,false) // Just localStorage
118 | clearVuexAlong(false,true) // Just sessionStorage
119 | ```
120 |
121 | ## RunDemo
122 |
123 | ```shell
124 | git clone https://github.com/boenfu/vuex-along.git
125 | cd ./vuex-along
126 | yarn run:demo
127 | ```
128 |
129 | ## Tips
130 |
131 | - Support `typescript`
132 | - Support `IE11`
133 | - `sessionStorage` data recovery takes precedence over `localStorage`
134 | - The `key` of the top-level object that stores the content is fixed to `root`
135 |
136 | ## Contribution
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | ## Maintainers
150 |
151 | - [boen](https://github.com/boenfu)
152 |
153 | ## License
154 |
155 | - [MIT](https://opensource.org/licenses/MIT)
156 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [English](./README.EN.md) | 简体中文
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | vuex-along - 持久化存储 state 的 vuex 扩展
14 | 常用于刷新网页后自动恢复 state
15 |
16 | # 目录
17 |
18 | - [安装](#安装)
19 | - [用法](#用法)
20 | - [示例](#示例)
21 | - [参数](#参数)
22 | - [数据清理](#数据清理)
23 | - [运行 demo](#运行demo)
24 | - [提示项](#提示项)
25 | - [贡献者](#贡献者)
26 | - [维护者](#维护者)
27 | - [License](#license)
28 |
29 | ## 安装
30 |
31 | ```shell
32 | npm install vuex-along --save
33 | # or
34 | yarn add vuex-along
35 | ```
36 |
37 | ## 用法
38 |
39 | ```javascript
40 | import createVuexAlong from 'vuex-along'
41 |
42 | export default new Vuex.Store({
43 | state:{...},
44 | modules:{...},
45 | plugins: [createVuexAlong()]
46 | });
47 | ```
48 |
49 | > 到此为止,插件已经生效了,默认会存储所有 state 到 localStorage
50 | >
51 | > 传入需要的 [参数](#参数) 来满足使用需求
52 | >- [微信小程序适配器](https://github.com/boenfu/vuex-along-wx-adapter)
53 | >- [数据加密适配器](https://github.com/boenfu/vuex-along-secure-adapter)
54 |
55 | ## 示例
56 |
57 | [→ 在线示例](https://boenfu.github.io/vuex-along/)
58 |
59 | ```javascript
60 | import createVuexAlong from "vuex-along";
61 |
62 | const moduleA = {
63 | state: {
64 | a1: "hello",
65 | a2: "world",
66 | },
67 | };
68 |
69 | const store = new Vuex.Store({
70 | state: {
71 | count: 0,
72 | },
73 | modules: {
74 | ma: moduleA,
75 | },
76 | plugins: [
77 | createVuexAlong({
78 | // 设置保存的集合名字,避免同站点下的多项目数据冲突
79 | name: "hello-vuex-along",
80 | local: {
81 | list: ["ma"],
82 | // 过滤模块 ma 数据, 将其他的存入 localStorage
83 | isFilter: true,
84 | },
85 | session: {
86 | // 保存模块 ma 中的 a1 到 sessionStorage
87 | list: ["ma.a1"],
88 | },
89 | }),
90 | ],
91 | });
92 | ```
93 |
94 | ## 参数
95 |
96 | #### VuexAlongOptions
97 |
98 | | **字段** | 必选 | 类型 | 描述 |
99 | | ----------- | ---- | ------- | ----------------------------------------- |
100 | | name | 否 | String | 设置本地数据集合的名字,默认为 vuex-along |
101 | | local | 否 | Object | localStorage 的配置,见 #WatchOptions |
102 | | session | 否 | Object | sessionStorage 的配置,见 #WatchOptions |
103 | | justSession | 否 | Boolean | 仅使用 sessionStorage |
104 |
105 | #### WatchOptions
106 |
107 | | 字段 | 必选 | 类型 | 描述 |
108 | | -------- | ---- | --------- | ------------------------------------ |
109 | | list | 是 | String [] | 需要监听的属性名或模块名的字符串列表 |
110 | | isFilter | 否 | Boolean | 过滤 list 中的字段而非保存 |
111 |
112 | ## 数据清理
113 |
114 | ```typescript
115 | window.clearVuexAlong(local = true, session = true):void;
116 | clearVuexAlong() // localStorage 和 sessionStorage 都会被清理
117 | clearVuexAlong(true,false) // 只清理 localStorage
118 | clearVuexAlong(false,true) // 只清理 sessionStorage
119 | ```
120 |
121 | ## 运行 demo
122 |
123 | ```shell
124 | git clone https://github.com/boenfu/vuex-along.git
125 | cd ./vuex-along
126 | yarn run:demo
127 | ```
128 |
129 | ## 提示项
130 |
131 | - 支持 `typescript`
132 | - `IE11`可用
133 | - `sessionStorage` 数据恢复优先级高于 `localStorage`
134 | - 存储内容的顶层对象的 `key` 固定为 `root`
135 |
136 | ## 贡献者们
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | ## 维护者
150 |
151 | - [boen](https://github.com/boenfu)
152 |
153 | ## License
154 |
155 | - [MIT](https://opensource.org/licenses/MIT)
156 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | "@babel/preset-env",
5 | {
6 | targets: {
7 | node: "current",
8 | },
9 | },
10 | ],
11 | ],
12 | plugins: ["@babel/plugin-transform-modules-commonjs"],
13 | };
14 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
4 | # local env files
5 | .env.local
6 | .env.*.local
7 |
8 | # Log files
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
13 | # Editor directories and files
14 | .idea
15 | .vscode
16 | *.suo
17 | *.ntvs*
18 | *.njsproj
19 | *.sln
20 | *.sw*
21 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/demo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
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 | "vue": "^2.6.6",
12 | "vue-router": "^3.0.3",
13 | "vuex": "^3.1.0",
14 | "vuex-along-secure-adapter": "^0.1.1"
15 | },
16 | "devDependencies": {
17 | "@vue/cli-plugin-babel": "^3.5.0",
18 | "@vue/cli-plugin-eslint": "^3.5.0",
19 | "@vue/cli-service": "^3.5.0",
20 | "babel-eslint": "^10.0.1",
21 | "eslint": "^5.8.0",
22 | "eslint-plugin-vue": "^5.0.0",
23 | "vue-template-compiler": "^2.5.21"
24 | },
25 | "eslintConfig": {
26 | "root": true,
27 | "env": {
28 | "node": true
29 | },
30 | "extends": [
31 | "plugin:vue/essential",
32 | "eslint:recommended"
33 | ],
34 | "rules": {},
35 | "parserOptions": {
36 | "parser": "babel-eslint"
37 | }
38 | },
39 | "postcss": {
40 | "plugins": {
41 | "autoprefixer": {}
42 | }
43 | },
44 | "browserslist": [
45 | "> 1%",
46 | "last 2 versions",
47 | "not ie <= 8"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boenfu/vuex-along/d95d8d685dc761fbf567887726637546fca531e1/demo/public/favicon.ico
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo
9 |
10 |
11 |
12 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home |
5 | More
6 |
7 |
8 |
9 |
10 |
11 |
21 |
--------------------------------------------------------------------------------
/demo/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boenfu/vuex-along/d95d8d685dc761fbf567887726637546fca531e1/demo/src/assets/logo.png
--------------------------------------------------------------------------------
/demo/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | Auto save vuex state to localStorage or sessionStorage,
6 | check out the
7 | github .
13 |
14 |
Local example
15 |
16 |
17 | {{ $store.state.count }}
18 | + 10
19 |
20 |
21 |
Session example
22 |
23 |
24 | {{ $store.state.ma.a1 }}
25 | reverse a1
26 | (Saved ma.a1)
27 |
28 |
29 | {{ $store.state.ma.a2 }}
30 | reverse a2
31 | (Unsaved ma.a2)
32 |
33 |
34 |
clear data
35 |
36 |
37 | clear local / 清除 localStorage
38 |
39 |
40 |
41 | clear Session / 清除 sessionStorage
42 |
43 |
44 |
45 |
54 |
55 |
56 | const moduleA = {
57 | state: {
58 | a1: "hello",
59 | a2: "world"
60 | }
61 | };
62 |
63 | const store = new Vuex.Store({
64 | state: {
65 | count: 0
66 | },
67 | mutations: {
68 | increment(state) {
69 | state.count += 10;
70 | },
71 | reverseA1(state) {
72 | state.ma.a1 = state.ma.a1
73 | .split("")
74 | .reverse()
75 | .join("");
76 | },
77 | reverseA2(state) {
78 | state.ma.a2 = state.ma.a2
79 | .split("")
80 | .reverse()
81 | .join("");
82 | }
83 | },
84 | modules: {
85 | ma: moduleA
86 | },
87 | plugins: [
88 |
89 |
90 | createVuexAlong({
91 | name: "hello-vuex-along",
92 | local: { list: ["ma"], isFilter: true },
93 | session: { list: ["ma.a1"] }
94 | })
95 |
96 |
97 | ]
98 | });
99 |
100 |
101 | More options
102 | check out the
103 | README .
109 |
110 |
111 |
112 |
113 |
138 |
139 |
140 |
164 |
--------------------------------------------------------------------------------
/demo/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 | import createVuexAlong from "../../lib/main";
4 | import SecureAdapter from "vuex-along-secure-adapter";
5 |
6 | import App from "./App.vue";
7 | import router from "./router";
8 |
9 | Vue.config.productionTip = false;
10 | Vue.use(Vuex);
11 |
12 | const moduleA = {
13 | state: {
14 | a1: "hello",
15 | a2: "world",
16 | },
17 | };
18 |
19 | const store = new Vuex.Store({
20 | state: {
21 | count: 0,
22 | },
23 | mutations: {
24 | increment(state) {
25 | state.count += 10;
26 | },
27 | reverseA1(state) {
28 | state.ma.a1 = state.ma.a1
29 | .split("")
30 | .reverse()
31 | .join("");
32 | },
33 | reverseA2(state) {
34 | state.ma.a2 = state.ma.a2
35 | .split("")
36 | .reverse()
37 | .join("");
38 | },
39 | },
40 | modules: {
41 | ma: moduleA,
42 | },
43 | plugins: [
44 | createVuexAlong({
45 | name: "hello-vuex-along",
46 | local: { list: ["ma"], isFilter: true },
47 | session: { list: ["ma.a1"] },
48 | adapterOptions: SecureAdapter(),
49 | }),
50 | ],
51 | });
52 |
53 | router.beforeEach((to, from, next) => {
54 | // eslint-disable-next-line
55 | console.info("router.beforeEach", {
56 | "router.app.$options.store.state.count":
57 | router.app.$options.store.state.count,
58 | });
59 | next();
60 | });
61 |
62 | new Vue({
63 | render: h => h(App),
64 | router,
65 | store,
66 | }).$mount("#app");
67 |
--------------------------------------------------------------------------------
/demo/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'home',
12 | component: Home
13 | },
14 | {
15 | path: '/more',
16 | name: 'more',
17 | // route level code-splitting
18 | // this generates a separate chunk (more.[hash].js) for this route
19 | // which is lazy-loaded when the route is visited.
20 | component: () => import(/* webpackChunkName: "more" */ './views/More.vue')
21 | }
22 | ]
23 | })
24 |
--------------------------------------------------------------------------------
/demo/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/demo/src/views/More.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/demo/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: "/vuex-along/",
3 | };
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | testMatch: ["/test/**/*.(spec|test).ts?(x)"],
4 | transform: {
5 | "^.+\\.js$": "babel-jest",
6 | "^.+\\.(ts|tsx)$": "ts-jest",
7 | },
8 | transformIgnorePatterns: [
9 | "/node_modules/(?!(lodash-es|other-es-lib))",
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boenfu/vuex-along/d95d8d685dc761fbf567887726637546fca531e1/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuex-along",
3 | "version": "1.2.13",
4 | "description": "Automatically restore vuex state after refreshing the page",
5 | "author": "boenfu",
6 | "main": "lib/main.js",
7 | "module": "lib/main.js",
8 | "types": "lib/main.d.ts",
9 | "private": false,
10 | "license": "MIT",
11 | "keywords": [
12 | "vue",
13 | "vuex",
14 | "localStorage"
15 | ],
16 | "files": [
17 | "lib"
18 | ],
19 | "devDependencies": {
20 | "@babel/core": "^7.7.4",
21 | "@babel/plugin-transform-modules-commonjs": "^7.7.4",
22 | "@babel/preset-env": "^7.7.4",
23 | "@types/jest": "^24.0.11",
24 | "babel-jest": "^24.9.0",
25 | "jest": "^24.7.1",
26 | "rimraf": "^2.6.3",
27 | "ts-jest": "^24.2.0",
28 | "typescript": "^3.7.2"
29 | },
30 | "scripts": {
31 | "build": "yarn build:plugins && yarn build:demo",
32 | "build:plugins": "tsc",
33 | "watch:plugins": "tsc --watch",
34 | "build:demo": "cd ./demo && yarn && yarn build",
35 | "run:demo": "cd ./demo && yarn && yarn serve",
36 | "publish-pages": "yarn build && ./scripts/publish-pages.sh",
37 | "test": "jest --clearCache && jest --silent --passWithNoTests"
38 | },
39 | "dependencies": {
40 | "@types/lodash-es": "^4.17.3",
41 | "@types/lowdb": "^1.0.9",
42 | "lodash-es": "^4.17.15",
43 | "lowdb": "^1.0.0"
44 | },
45 | "repository": {
46 | "type": "git",
47 | "url": "git+https://github.com/boenfu/vuex-along.git"
48 | },
49 | "bugs": {
50 | "url": "https://github.com/boenfu/vuex-along/issues"
51 | },
52 | "homepage": "https://github.com/boenfu/vuex-along#readme",
53 | "eslintConfig": {
54 | "parserOptions": {
55 | "ecmaVersion": 7,
56 | "sourceType": "module"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/scripts/publish-pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euv
3 | cd ../demo/dist/
4 | git init
5 | git remote add pb git@github.com:boenfu/vuex-along.git || true
6 | git add .
7 | git commit -m "publish gh-pages"
8 | git push pb master:gh-pages -f
9 |
--------------------------------------------------------------------------------
/src/adapters/SessionStorage.ts:
--------------------------------------------------------------------------------
1 | import { AdapterSync, AdapterOptions } from "lowdb";
2 |
3 | const stringify = (obj: unknown) => JSON.stringify(obj, null, 2);
4 |
5 | class SessionStorage {
6 | defaultValue: TSchema;
7 | serialize: (data: TSchema) => string;
8 | deserialize: (serializedData: string) => TSchema;
9 |
10 | constructor(public source: string, options?: AdapterOptions) {
11 | this.source = source;
12 | this.defaultValue = options?.defaultValue ?? {};
13 | this.serialize = options?.serialize ?? stringify;
14 | this.deserialize = options?.deserialize ?? JSON.parse;
15 | }
16 |
17 | read(): TSchema {
18 | const data = sessionStorage.getItem(this.source);
19 |
20 | if (!data) {
21 | sessionStorage.setItem(this.source, this.serialize(this.defaultValue));
22 | return this.defaultValue;
23 | }
24 |
25 | return this.deserialize(data);
26 | }
27 |
28 | write(data: TSchema): void {
29 | sessionStorage.setItem(this.source, this.serialize(data));
30 | }
31 | }
32 |
33 | export default (SessionStorage as unknown) as AdapterSync;
34 |
--------------------------------------------------------------------------------
/src/db.ts:
--------------------------------------------------------------------------------
1 | import low, { AdapterSync, LowdbSync, AdapterAsync, LowdbAsync } from "lowdb";
2 |
3 | type Lowdb = LowdbSync | LowdbAsync;
4 |
5 | export type LowdbAdapter =
6 | | AdapterAsync
7 | | AdapterSync;
8 |
9 | export class DBService {
10 | db!: Lowdb;
11 | ready: Promise;
12 |
13 | constructor(private _name: string, Adapter: LowdbAdapter) {
14 | this.ready = this.initialize(Adapter);
15 | }
16 |
17 | private async initialize(Adapter: LowdbAdapter): Promise {
18 | let name = this._name;
19 |
20 | let adapter = new Adapter(name) as LowdbAdapter;
21 |
22 | this.db = (!("then" in adapter)
23 | ? low(adapter)
24 | : await low(adapter)) as Lowdb;
25 | }
26 |
27 | get(key: string): T | undefined {
28 | return this.db.get(key).value();
29 | }
30 |
31 | async set(key: string, value: T): Promise {
32 | await this.db.set(key, value).write();
33 | }
34 |
35 | async unset(key: string): Promise {
36 | await this.db.unset(key).write();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import defaultsDeep from "lodash-es/defaultsDeep";
2 | import omit from "lodash-es/omit";
3 | import pick from "lodash-es/pick";
4 | import cloneDeep from "lodash-es/cloneDeep";
5 |
6 | import { DBService, LowdbAdapter } from "./db";
7 | import LocalStorage from "lowdb/adapters/LocalStorage";
8 | import SessionStorage from "./adapters/SessionStorage";
9 |
10 | const DEFAULT_NAME = "vuex-along";
11 | const ROOT_KEY = "root";
12 |
13 | interface Store {
14 | state: object;
15 | replaceState(state: object): void;
16 | subscribe(callback: (mutation: unknown, state: object) => void): void;
17 | }
18 |
19 | export interface VuexAlongWatchOptions {
20 | list: string[];
21 | isFilter?: boolean;
22 | }
23 |
24 | export type VuexAlongAdapterOptions = {
25 | local?: LowdbAdapter;
26 | session?: LowdbAdapter;
27 | sync?: boolean;
28 | };
29 |
30 | export interface VuexAlongOptions {
31 | name?: string;
32 | local?: VuexAlongWatchOptions;
33 | session?: VuexAlongWatchOptions;
34 | justSession?: boolean;
35 | adapterOptions?: VuexAlongAdapterOptions;
36 | }
37 |
38 | class VuexAlong {
39 | readonly localDBService: DBService | undefined;
40 | readonly sessionDBService: DBService | undefined;
41 |
42 | private local: VuexAlongWatchOptions | undefined;
43 | private session: VuexAlongWatchOptions | undefined;
44 | private justSession: boolean;
45 | private sync: boolean;
46 |
47 | get ready(): Promise<[void, void]> | true {
48 | if (this.sync) {
49 | return true;
50 | }
51 |
52 | return Promise.all([
53 | this.localDBService?.ready,
54 | this.sessionDBService?.ready,
55 | ]);
56 | }
57 |
58 | constructor({
59 | local,
60 | session,
61 | name = DEFAULT_NAME,
62 | justSession = false,
63 | adapterOptions,
64 | }: VuexAlongOptions) {
65 | let {
66 | local: localAdapter = LocalStorage,
67 | session: sessionAdapter = SessionStorage,
68 | // Make sure your adapter is syncing. Then you can get the synchronized state
69 | sync = true,
70 | } = adapterOptions || {};
71 |
72 | this.local = local;
73 | this.session = session;
74 | this.justSession = justSession;
75 | this.sync = !!((!localAdapter && !sessionAdapter) || sync);
76 |
77 | if (!justSession) {
78 | this.localDBService = new DBService(name, localAdapter);
79 | }
80 |
81 | if (session) {
82 | this.sessionDBService = new DBService(name, sessionAdapter);
83 | }
84 |
85 | if (window) {
86 | window.clearVuexAlong = (local, session) => {
87 | this.clear(local, session);
88 | };
89 | }
90 | }
91 |
92 | private saveLocalData(state: object): void {
93 | if (this.justSession) {
94 | return;
95 | }
96 |
97 | this._dataHandler(state, this.local ?? { list: [] }, this.localDBService);
98 | }
99 |
100 | private saveSessionData(state: object): void {
101 | let session = this.session;
102 |
103 | if (!session) {
104 | return;
105 | }
106 |
107 | this._dataHandler(state, session, this.sessionDBService);
108 | }
109 |
110 | private _dataHandler(
111 | state: object,
112 | options: VuexAlongWatchOptions,
113 | db: DBService | undefined
114 | ): void {
115 | if (!db) {
116 | return;
117 | }
118 |
119 | let duplicateState = cloneDeep(state);
120 | let key = ROOT_KEY;
121 |
122 | let { list, isFilter } = options;
123 |
124 | if (list?.length) {
125 | duplicateState = isFilter
126 | ? omit(duplicateState, list)
127 | : pick(duplicateState, list);
128 | }
129 |
130 | db.set(key, duplicateState).catch(logger);
131 | }
132 |
133 | saveData(state: object): void {
134 | this.saveLocalData(state);
135 | this.saveSessionData(state);
136 | }
137 |
138 | restoreData(store: Store): void {
139 | let key = ROOT_KEY;
140 |
141 | store.replaceState(
142 | defaultsDeep(
143 | this.sessionDBService?.get(key),
144 | this.localDBService?.get(key),
145 | store.state
146 | )
147 | );
148 | }
149 |
150 | clear(local = true, session = false): void {
151 | local && this.localDBService?.unset(ROOT_KEY).catch(logger);
152 | session && this.sessionDBService?.unset(ROOT_KEY).catch(logger);
153 | }
154 | }
155 |
156 | export default function (
157 | options: VuexAlongOptions = {}
158 | ): (store: Store) => void {
159 | let vuexAlong = new VuexAlong(options);
160 |
161 | return async (store: Store): Promise => {
162 | if (vuexAlong.ready !== true) {
163 | await vuexAlong.ready;
164 | }
165 | vuexAlong.restoreData(store);
166 | store.subscribe((_mutation, state) => vuexAlong.saveData(state));
167 | };
168 | }
169 |
170 | // utils
171 |
172 | function logger(reason: any): void {
173 | console.error(reason);
174 | }
175 |
--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | clearVuexAlong: (local?: boolean, session?: boolean) => void;
3 | }
4 |
--------------------------------------------------------------------------------
/test/db.test.ts:
--------------------------------------------------------------------------------
1 | import LocalStorage from "lowdb/adapters/LocalStorage";
2 |
3 | import { DBService } from "../src/db";
4 | import SessionStorage from "../src/adapters/SessionStorage";
5 |
6 | test("test local db", async () => {
7 | const local = new DBService("local", LocalStorage);
8 |
9 | await local.ready;
10 |
11 | local.set("a", 1);
12 |
13 | expect(local.get("a")).toBe(1);
14 |
15 | local.unset("a");
16 |
17 | expect(local.get("a")).toBe(undefined);
18 | });
19 |
20 | test("test session db", async () => {
21 | const session = new DBService("session", SessionStorage);
22 |
23 | await session.ready;
24 |
25 | session.set("b", 2);
26 |
27 | expect(session.get("b")).toBe(2);
28 |
29 | session.unset("b");
30 |
31 | expect(session.get("b")).toBe(undefined);
32 | });
33 |
--------------------------------------------------------------------------------
/test/main.test.ts:
--------------------------------------------------------------------------------
1 | import create from "../src/main";
2 |
3 | test("create vuex-along", () => {
4 | const vuexAlong = create();
5 |
6 | expect(typeof vuexAlong).toBe("function");
7 | });
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "ES2015",
5 | "declaration": true,
6 | "outDir": "./lib",
7 | "rootDir": "./src",
8 | "listFiles": true,
9 | "strict": true,
10 | "allowSyntheticDefaultImports": true,
11 | "esModuleInterop": true,
12 | "allowJs": true,
13 | "skipDefaultLibCheck": true,
14 | "skipLibCheck": true,
15 | "lib": ["es6", "es2015", "dom", "scripthost"]
16 | },
17 | "include": ["src"]
18 | }
19 |
--------------------------------------------------------------------------------