Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/gitbreaker222/SvelteStore
Template for client side svelte store
https://github.com/gitbreaker222/SvelteStore
state-management storage svelte
Last synced: 28 days ago
JSON representation
Template for client side svelte store
- Host: GitHub
- URL: https://github.com/gitbreaker222/SvelteStore
- Owner: gitbreaker222
- License: cc0-1.0
- Created: 2020-02-23T21:50:47.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2023-10-17T03:37:15.000Z (about 1 year ago)
- Last Synced: 2024-04-23T13:13:44.353Z (8 months ago)
- Topics: state-management, storage, svelte
- Language: JavaScript
- Homepage: https://svelte.dev/repl/a76e9e11af784185a39020fec02b7733?version=3.31.2
- Size: 4.13 MB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-svelte-stores - SvelteStore
README
[![Node.js CI](https://github.com/gitbreaker222/SvelteStore/actions/workflows/node.js.yml/badge.svg?branch=master&event=push)](https://github.com/gitbreaker222/SvelteStore/actions/workflows/node.js.yml)
# Svelte Store
![logo](./docs/Svelte%20Store%20Logo.png)
Template for client side svelte store _(unofficial)_
![store internal diagram](./docs/Svelte%20Store.png)
live demo: https://svelte.dev/repl/a76e9e11af784185a39020fec02b7733?version=3.31.2
## Quick start
```bash
npm install
npm run dev
```Navigate to [localhost:5000](http://localhost:5000) and open dev-tools.
## Usage
- Copy all `src/store/_svelteStore.*` files in your project
- For production builds _rollup_ uses tree-shaking to ignore the debug version π
- Create a new file `myStore.js` based on `src/store/templateStore.js` next to `_svelteStore.js`
- In `myStore.js` replace all "templateStore" with "myStore"
- Delete everything below "Demo-Actions"
- Define initial state in `State` as simple JSON
- Write actions that call `storeIn.update(actionName, updaterFn)`## Concept
![architecture concept](./docs/Svelte%20Store%20Architecture%20Concept.png)
Svelte Store aims for *separation of concerns* by covering everything needed to run a client-side application without any UI. Think of it as the CLI to your Web-App.
## Features
For detailed insight of *changes* or the *current state* , all you need is your browsers dev-tools. No plugins, zero 0οΈβ£ dependencies _(besides svelte)_.
- βοΈ Track state diffs
- π Inspect current state
- β οΈ Type warnings
- π Persistent storage with a single switch
- βΎοΈ Infinite loop detection
- π Testable Actions
- π Audible activity### βοΈ Before/After diffs on state updates:
See what has been changed over time. *This is a debugging feature and deactivated in prod-mode.*
![logs](./docs/logs.png)
### π Full state in SessionStorage
See the full state tree to understand the current state behind the GUI. *This is a debugging feature and deactivated in prod-mode.*
![full state](./docs/full-state.png)
Learn more about Storage-Inspector:
- Firefox: https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector
- Chrome: https://developer.chrome.com/docs/devtools/storage/sessionstorage/### β οΈ Automatic type warnings on state updates:
The initial `State` of a SvelteStore also acts as type definition for the top level fields. If an action updates a field with another type, a warning will be shown in dev-tools console. No replacement for TypeScript, but free basic type checks. *This is a debugging feature and deactivated in prod-mode.*
![type warning](./docs/type%20warning.png)
Learn more about native JS types at [Mozilla Developer Network: typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#description)
### π Persist in web-storage
The state can optionally persisted in localStorage by creating a store with the `persist` flag. Useful for data, that should be rememberd after a page reload or across tabs.
```js
const [storeIn, storeOut] = useStore(new State(), {
name: "templateStore",
persist: true,
})
```
### βΎοΈ Infinite loop detectionSvelteStore can break unwanted endless circles of action calls after about 3 seconds, if an action gets called with an interval of < 150 ms.
This feature can be turned off in `_svelteStore.js` with `settings.loopGuard: false`. *This is a debugging feature and deactivated in prod-mode.*
![confirm dialog asking to reload when action is called infinitely](./docs/infinite%20loop%20detection.png)
[screen recording of stopping infinite loops with a confirm dialog about reloading the window](./docs/infinite%20loop%20detection.webm)If the users confirms the reload, the window is asked to reload and an error is thrown, to break e.g. `for` loops. If the dialog is canceled, the action gets ignored for 150 ms, so a long task may finish.
### π Testable Actions
SvelteStore gives you a hand getting startet with unit tests for _actions_. It's a good advice, to keep the "reset" action from the templateStore, so you can reset or override the default state before every test.
![screenshot of VS-Code. The test stopped for debugging at a breakpoint set in an action](docs/svelteStore%20test%20debugger.png)
The setup in this demo-app is based on [this article][test-article] / [testing-library.com][testing-library] and uses [jest](https://jestjs.io/).
[test-article]: https://dev.to/jpblancodb/testing-svelte-components-with-jest-53h3
[testing-library]: https://testing-library.com/docs/svelte-testing-library/intro.Go for a test ride with `npm test` or `npm run test:watch` to automatically rerun the tests on file-save β‘.
To write a new test for an action:
1. Arrange: `reset()` the state and optionally override it
1. Act: Call an action an safe the returned new state
1. Assert: Write an expactation for the new stateSee [templateStore.test.js](./src/store/templateStore.test.js) for some examples.
### π Audible activity
When `settings.tickLog` in `_svelteStore.js` is turned on, every action makes a "tick"/"click" sound. Inspired by detectors for radio-activity β’οΈ, this way you simply hear, when too much is going on. Louder clicks mean more updates at the same time. Of course only in dev-mode.
### Environments: Dev / Prod
No debugging-functions in production / test-runs, to improve performance. `_svelteStore.js` returns the debug version only if `process.env.NODE_ENV === 'debug'`.
|command|`NODE_ENV` value|config by|
|---|---|---|
|`npm run dev`|debug|`rollup.config.js`|
|`npm run build`|prod|`rollup.config.js`|
|`npm test`|test|[jest](https://jestjs.io/docs/getting-started#using-babel)|## Two Rules π
1. IMMUTABLE
1. PURE UPDATES### #1 - IMMUTABLE:
When your actions change something (state Object, a list inside state, etc...), **make a shallow copy of it!**
good:
```javascript
let { list } = state;list = [...list]; // shallow copy with spread syntax
list.push(1234);return { ...state, list };
```bad: *mutation*
```javascript
let { list } = state;// mutated objects won't be detected as a change
list.push(1234);return { ...state, list };
```bad: *deep copy*
```javascript
let { list } = deepCopy(state);
// EVERY object will look like a change
// Svelte must re-render everything instead just "list"list.push(1234);
return { ...state, list };
```### #2 - PURE UPDATES:
The callbacks for `storeIn.update` must not have side-effects and return a shallow-copy-state.
Every update modifies state, so if you want to bundle **multiple actions**, run them one by one - not nested:
good:
```javascript
export const multiAction1 = () => {
actionA()
actionB()
// Return last update
return storeIn.update('actionC', function (state) {
let { xy } = state
β¦
return { ...state, xy}
});
}export const multiAction2 = () => {
let state = storeOut.get()
let { xy } = state// A or B depending on current state
if (xy) {
actionA()
} else {
storeIn.update('actionB', function (state) {
let { xy } = state
β¦
return { ...state, xy}
});
}
// Return last update
return actionC()
}export const multiAction3 = async () => {
// Follow the state of "xy"
let state = storeOut.get()
let { xy } = state // xy = trueif (xy) await asyncActionA()
// Re-assign updated state when using it
// Beware that this practise may leads to bugs (see bad multiAction3 below)
state = storeIn.update('actionB', function (state) {
let { xy } = state
xy = await api.fetch(xy) // xy = false
return { ...state, xy}
});xy = state.xy // xy = false (!! don't forget to re-assign)
if (xy) return asyncActionC()return state
}
```bad:
```javascript
export const multiAction1 = () => {
// Nested actions are side-effects
return storeIn.update('actionA', function (state) {
let { xy } = state
state = actionB() //! Don't call functions inside "update"
actionC() // ...messes up state easily; not pure
β¦
return { ...state, xy}
});
}export const multiAction3 = async () => {
// Follow the state of "xy"
let state = storeOut.get()
let { xy } = state // xy = trueif (xy) await asyncActionA()
state = storeIn.update('actionB', function (state) {
let { xy } = state
xy = await api.fetch(xy) // xy = false
return { ...state, xy}
});// xy is still what it was before actionB.
// (See good: multiAction3 above)
if (xy) return asyncActionC() // xy = true (expected false)return state
}
```## Anatomy of an action
![Anatomy of an action](./docs/Svelte%20Store%20Action%20Anatomy.png)