├── .npmrc ├── iky.png ├── .prettierrc ├── .gitignore ├── .github └── dependabot.yml ├── jsconfig.json ├── LICENSE ├── package.json ├── README.md └── src └── lib └── index.js /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /iky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/furudean/svelte-persistent-store/HEAD/iky.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "semi": false, 4 | "singleQuote": false, 5 | "trailingComma": "none", 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | .vscode/settings.json 10 | /dist 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | versioning-strategy: increase-if-necessary 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cassidy Bandy 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@furudean/svelte-persistent-store", 3 | "version": "0.9.0", 4 | "description": "Svelte store that saves and loads data from localStorage or sessionStorage", 5 | "keywords": [ 6 | "svelte", 7 | "svelte-kit", 8 | "store", 9 | "localStorage", 10 | "sessionStorage" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/furudean/svelte-persistent-store.git" 15 | }, 16 | "license": "MIT", 17 | "funding": "https://github.com/sponsors/furudean", 18 | "author": "Merilynn Bandy (https://www.furudean.com/)", 19 | "type": "module", 20 | "scripts": { 21 | "check": "svelte-check --tsconfig ./jsconfig.json", 22 | "check:watch": "svelte-check --tsconfig ./jsconfig.json --watch", 23 | "format": "prettier --ignore-path .gitignore --write .", 24 | "lint": "prettier --ignore-path .gitignore --check .", 25 | "package": "svelte-package" 26 | }, 27 | "exports": { 28 | ".": { 29 | "types": "./dist/index.d.ts", 30 | "svelte": "./dist/index.js" 31 | } 32 | }, 33 | "files": [ 34 | "dist" 35 | ], 36 | "devDependencies": { 37 | "@sveltejs/package": "^2.3.0", 38 | "prettier": "^3.2.5", 39 | "svelte": "^4.2.12", 40 | "svelte-check": "^3.6.8", 41 | "typescript": "~5.4.3" 42 | }, 43 | "peerDependencies": { 44 | "svelte": "^3 || ^4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A girl 6 | 7 | # [svelte-persistent-store](https://www.npmjs.com/package/@furudean/svelte-persistent-store) 8 | 9 | This is a 10 | [writable svelte store](https://svelte.dev/docs#run-time-svelte-store-writable) 11 | that saves and loads data from `Window.localStorage` or `Window.sessionStorage`. 12 | Works with Svelte Kit out of the box. 13 | 14 | The store listens to events from the `Storage` interface and will sync its 15 | internal state upon changes. This makes debugging using the developer console 16 | easy, and it will update across sessions as well. 17 | 18 | ## Install 19 | 20 | ```bash 21 | npm install @furudean/svelte-persistent-store 22 | ``` 23 | 24 | ## Use 25 | 26 | > **Note**: By default only 27 | > [JSON serializable values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) 28 | > are handled, but [custom serialization and deserialization functions can be 29 | > provided](#custom-serialization-functions). 30 | 31 | ```js 32 | import { persistent } from "@furudean/svelte-persistent-store" 33 | 34 | const preferences = persistent({ 35 | start_value: { 36 | foo: "bar" 37 | }, 38 | key: "preferences", // key to save as in Storage 39 | storage_type: "localStorage" // Storage object to use 40 | }) 41 | ``` 42 | 43 | ## Custom serialization functions 44 | 45 | Since the `Storage` interface only supports strings, data needs to be converted 46 | to strings before saving. By default `JSON.stringify` and `JSON.parse` is used. 47 | 48 | You can pass custom serializer and deserializer functions if you require 49 | specific behavior when loading or saving data from `Storage`. For example, you 50 | can handle `Date`s like this: 51 | 52 | ```js 53 | const persistent_date = persistent({ 54 | start_value: new Date(), 55 | key: "my-persistent-date", 56 | storage_type: "localStorage", 57 | serialize: (date) => date.toISOString(), // transform before saving 58 | deserialize: (str) => new Date(str) // transform after loading 59 | }) 60 | ``` 61 | -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store" 2 | 3 | /** 4 | * @template {unknown} T 5 | * @typedef {Object} Options 6 | * 7 | * @property {T} start_value 8 | * If `Storage` is empty, this is the value used. 9 | * 10 | * @property {string} key 11 | * Key to save as in `Storage`. 12 | * 13 | * @property {"localStorage" | "sessionStorage"} storage_type 14 | * `Storage` object to use. 15 | * 16 | * @property {(value: T) => string} [serialize] 17 | * Function used to convert data before saving to `Storage`. Defaults to 18 | * `JSON.stringify`. 19 | * 20 | * @property {(value: string) => T} [deserialize] 21 | * Function used to convert `Storage` to data. Defaults to `JSON.parse`. 22 | */ 23 | 24 | const DEFAULT_OPTIONS = Object.freeze({ 25 | serialize: JSON.stringify, 26 | deserialize: JSON.parse 27 | }) 28 | 29 | /** 30 | * Store that saves and loads data from `localStorage` or `sessionStorage`. 31 | * 32 | * If the `Storage` interface is updated the store state will stay in sync. 33 | * 34 | * @see https://github.com/furudean/svelte-persistent-store 35 | * 36 | * @template {unknown} T 37 | * 38 | * @param {Options} options 39 | */ 40 | export function persistent(options) { 41 | /** @type {Required>} */ 42 | const _options = { ...DEFAULT_OPTIONS, ...options } 43 | const { key, storage_type, start_value, serialize, deserialize } = _options 44 | 45 | const storage = 46 | typeof window !== "undefined" 47 | ? window[storage_type] ?? undefined 48 | : undefined 49 | 50 | const store = writable(start_value, function start() { 51 | /** @param {StorageEvent} event */ 52 | function storage_handler(event) { 53 | if (event.key === key) sync() 54 | } 55 | 56 | // bail if storage is missing, this will be the case during server 57 | // rendering 58 | if (!storage) return 59 | 60 | sync() 61 | 62 | window.addEventListener("storage", storage_handler) 63 | 64 | return function stop() { 65 | window.removeEventListener("storage", storage_handler) 66 | } 67 | }) 68 | 69 | /** 70 | * Set store value and web storage 71 | * @param value {T} 72 | */ 73 | function set(value) { 74 | store.set(value) 75 | storage?.setItem(key, serialize(value)) 76 | } 77 | 78 | /** 79 | * Update store value and web storage 80 | * @param {(value: T) => T} updater 81 | */ 82 | function update(updater) { 83 | store.update((current_value) => { 84 | const new_value = updater(current_value) 85 | 86 | storage?.setItem(key, serialize(new_value)) 87 | 88 | return new_value 89 | }) 90 | } 91 | 92 | /** Reconcile store value with web storage */ 93 | function sync() { 94 | const stored_data = storage?.getItem(key) 95 | 96 | if (stored_data === null || stored_data === undefined) { 97 | set(start_value) 98 | } else { 99 | // only set store value, otherwise we trigger a double sync 100 | store.set(deserialize(stored_data)) 101 | } 102 | } 103 | 104 | return { 105 | set, 106 | update, 107 | subscribe: store.subscribe 108 | } 109 | } 110 | --------------------------------------------------------------------------------