@veams/status-quo 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +132 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/README.md +130 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/state-factory.d.ts +2 -0
- package/dist/hooks/state-factory.js +10 -0
- package/dist/hooks/state-factory.js.map +1 -0
- package/dist/hooks/state-singleton.d.ts +2 -0
- package/dist/hooks/state-singleton.js +9 -0
- package/dist/hooks/state-singleton.js.map +1 -0
- package/dist/hooks/state-subscription.d.ts +3 -0
- package/dist/hooks/state-subscription.js +15 -0
- package/dist/hooks/state-subscription.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/store/__tests__/state-handler.spec.d.ts +1 -0
- package/dist/store/__tests__/state-handler.spec.js +85 -0
- package/dist/store/__tests__/state-handler.spec.js.map +1 -0
- package/dist/store/dev-tools.d.ts +23 -0
- package/dist/store/dev-tools.js +16 -0
- package/dist/store/dev-tools.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.js +3 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/state-handler.d.ts +36 -0
- package/dist/store/state-handler.js +122 -0
- package/dist/store/state-handler.js.map +1 -0
- package/dist/store/state-singleton.d.ts +5 -0
- package/dist/store/state-singleton.js +12 -0
- package/dist/store/state-singleton.js.map +1 -0
- package/dist/types/types.d.ts +7 -0
- package/dist/types/types.js +2 -0
- package/dist/types/types.js.map +1 -0
- package/jest.config.ci.cjs +7 -0
- package/jest.config.cjs +22 -0
- package/package.json +79 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/state-factory.tsx +17 -0
- package/src/hooks/state-singleton.tsx +14 -0
- package/src/hooks/state-subscription.tsx +25 -0
- package/src/index.ts +9 -0
- package/src/store/__tests__/state-handler.spec.ts +108 -0
- package/src/store/dev-tools.ts +44 -0
- package/src/store/index.ts +3 -0
- package/src/store/state-handler.ts +181 -0
- package/src/store/state-singleton.ts +21 -0
- package/src/types/types.ts +8 -0
- package/tsconfig.json +42 -0
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
|
4
|
+
plugins: ['react', '@typescript-eslint', 'simple-import-sort'],
|
|
5
|
+
ignorePatterns: [
|
|
6
|
+
'.eslintrc.js',
|
|
7
|
+
'*.config.js',
|
|
8
|
+
'setupTests.js',
|
|
9
|
+
'setupTests.ts',
|
|
10
|
+
'env.js',
|
|
11
|
+
'env.local.js',
|
|
12
|
+
],
|
|
13
|
+
extends: [
|
|
14
|
+
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin
|
|
15
|
+
'airbnb-base',
|
|
16
|
+
'airbnb-typescript/base',
|
|
17
|
+
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
|
18
|
+
'plugin:react/recommended',
|
|
19
|
+
'airbnb',
|
|
20
|
+
'airbnb/hooks',
|
|
21
|
+
'airbnb-typescript',
|
|
22
|
+
// Enables eslint-plugin-prettier and displays prettier errors as ESLint errors.
|
|
23
|
+
// Make sure this is always the last configuration in the extends array.
|
|
24
|
+
'plugin:prettier/recommended',
|
|
25
|
+
],
|
|
26
|
+
parserOptions: {
|
|
27
|
+
project: './tsconfig.json',
|
|
28
|
+
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
|
29
|
+
sourceType: 'module', // Allows for the use of imports
|
|
30
|
+
ecmaFeatures: {
|
|
31
|
+
jsx: true, // Allows for the parsing of JSX
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
settings: {
|
|
35
|
+
react: {
|
|
36
|
+
version: 'detect', // React version. "detect" automatically picks the version you have installed.
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
|
40
|
+
rules: {
|
|
41
|
+
'max-classes-per-file': ['error', 2],
|
|
42
|
+
'no-console': 'off',
|
|
43
|
+
'@typescript-eslint/consistent-type-imports': 'error',
|
|
44
|
+
'no-param-reassign': ['error', {props: false}], // for reducer and simple reference changes
|
|
45
|
+
'import/order': 'off', // Is handled by simple-import-sort
|
|
46
|
+
'import/prefer-default-export': 'off', // This is not really useful, because named exports are easier to import (IDE)
|
|
47
|
+
'import/no-default-export': 'error', // Prefer named exports over default exports since they are easier to find and refactor
|
|
48
|
+
'import/extensions': [
|
|
49
|
+
'error',
|
|
50
|
+
'always',
|
|
51
|
+
{
|
|
52
|
+
ignorePackages: true,
|
|
53
|
+
js: 'always',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
'simple-import-sort/exports': 'error',
|
|
57
|
+
'simple-import-sort/imports': [
|
|
58
|
+
'error',
|
|
59
|
+
{
|
|
60
|
+
/**
|
|
61
|
+
* The default grouping, but with type imports last as a separate group.
|
|
62
|
+
* From https://github.com/lydell/eslint-plugin-simple-import-sort/blob/37f9448cdfed85dacf27e34c515653ff96f0377a/examples/.eslintrc.js.
|
|
63
|
+
*/
|
|
64
|
+
groups: [['^\\u0000'], ['^@?\\w'], ['^'], ['^\\.'], ['^.+\\u0000$']],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
'@typescript-eslint/no-use-before-define': ['error', {functions: false}], // function declarations are always
|
|
68
|
+
// hoisted so it's safe
|
|
69
|
+
'@typescript-eslint/lines-between-class-members': [
|
|
70
|
+
'error',
|
|
71
|
+
'always',
|
|
72
|
+
{exceptAfterSingleLine: true},
|
|
73
|
+
], // Avoid blowing up classes
|
|
74
|
+
|
|
75
|
+
// Forbid the use of extraneous packages
|
|
76
|
+
// https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
|
|
77
|
+
// paths are treated both as absolute paths, and relative to process.cwd()
|
|
78
|
+
'import/no-extraneous-dependencies': [
|
|
79
|
+
'error',
|
|
80
|
+
{
|
|
81
|
+
devDependencies: [
|
|
82
|
+
'**/setupTests.{js,ts}', // test setup files
|
|
83
|
+
'test/**', // tape, common npm pattern
|
|
84
|
+
'tests/**', // also common npm pattern
|
|
85
|
+
'spec/**', // mocha, rspec-like pattern
|
|
86
|
+
'**/__tests__/**', // jest pattern
|
|
87
|
+
'**/__mocks__/**', // jest pattern
|
|
88
|
+
'test.{js,jsx}', // repos with a single test file
|
|
89
|
+
'test-*.{js,jsx}', // repos with multiple top-level test files
|
|
90
|
+
'**/*{.,_}{test,spec}.{js,jsx}', // tests where the extension or filename suffix denotes that it is a test
|
|
91
|
+
'**/jest.config.js', // jest config
|
|
92
|
+
'**/jest.setup.js', // jest setup
|
|
93
|
+
'**/vue.config.js', // vue-cli config
|
|
94
|
+
'**/webpack.config.js', // webpack config
|
|
95
|
+
'**/webpack.config.*.js', // webpack config
|
|
96
|
+
'**/rollup.config.js', // rollup config
|
|
97
|
+
'**/rollup.config.*.js', // rollup config
|
|
98
|
+
'**/gulpfile.js', // gulp config
|
|
99
|
+
'**/gulpfile.*.js', // gulp config
|
|
100
|
+
'**/Gruntfile{,.js}', // grunt config
|
|
101
|
+
'**/protractor.conf.js', // protractor config
|
|
102
|
+
'**/protractor.conf.*.js', // protractor config
|
|
103
|
+
'**/karma.conf.js', // karma config
|
|
104
|
+
'**/.eslintrc.js', // eslint config
|
|
105
|
+
],
|
|
106
|
+
optionalDependencies: false,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
'react/prop-types': 'off', // Since we do not use prop-types
|
|
110
|
+
'react/require-default-props': 'off', // Since we do not use prop-types
|
|
111
|
+
// Many of our loops are server side rendered, so we can rely on the index in general
|
|
112
|
+
'react/no-array-index-key': 0,
|
|
113
|
+
// To support hydration of components, a string is necessary so that the minification of bundles
|
|
114
|
+
// do not affect our markup generation on the server.
|
|
115
|
+
'react/display-name': [2, {ignoreTranspilerName: true}],
|
|
116
|
+
// aria roles ignored (0) instead of warning (1) / errors (2).
|
|
117
|
+
'jsx-a11y/role-supports-aria-props': 0,
|
|
118
|
+
'react/function-component-definition': [2, {namedComponents: 'arrow-function'}],
|
|
119
|
+
// Conditional spreads are easier to do so we can deactivate this rule
|
|
120
|
+
'react/jsx-props-no-spreading': 0,
|
|
121
|
+
// Enforce the definition of Fragment instead of shorthand syntax.
|
|
122
|
+
// The thing is, that keys can only be applied to the long version. So we should stick to one version.
|
|
123
|
+
'react/jsx-fragments': [2, 'element'],
|
|
124
|
+
// We need to use setDangerouslyInnerHtml for article and server side rendered markup prepared by external helpers.
|
|
125
|
+
// So it makes no sense to have this rule in place.
|
|
126
|
+
'react/no-danger': 0,
|
|
127
|
+
// strict null-checking is not necessary.
|
|
128
|
+
// The syntax itself should be avoided for sure but in some cases where we know we get the data,
|
|
129
|
+
// we can use this functionality
|
|
130
|
+
'@typescript-eslint/no-non-null-assertion': 0,
|
|
131
|
+
},
|
|
132
|
+
};
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v20
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Status Quo (`@veams/status-quo`)
|
|
2
|
+
|
|
3
|
+
The `Manager` to rule your state.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Content
|
|
8
|
+
|
|
9
|
+
1. [Getting Started](#getting-started)
|
|
10
|
+
2. [Example](#example)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Getting Started
|
|
15
|
+
|
|
16
|
+
1. Create your own state handler which handles all the streams and a state you expose next to the actions
|
|
17
|
+
1. Use actions and state in your component
|
|
18
|
+
1. When using React, initialize the state handler with a custom hook called `useStateFactory()` (or `useStateSingleton()` for Singleton states)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
These three steps are necessary to create a completely decoupled state management solution without the need of creating custom hooks with `useEffect()`.
|
|
22
|
+
|
|
23
|
+
__Note__:
|
|
24
|
+
_Please keep in mind that dependencies for the hook needs to be flattened and cannot be used as an object due to how React works._
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
Let's start with a simple state example.
|
|
29
|
+
You should start with the abstract class `BaseState`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { useStateFactory, StateHandler } from '@veams/status-quo';
|
|
33
|
+
|
|
34
|
+
type CounterState = {
|
|
35
|
+
count: number;
|
|
36
|
+
};
|
|
37
|
+
type CounterActions = {
|
|
38
|
+
increase: () => void;
|
|
39
|
+
decrease: () => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
class CounterStateHandler extends StateHandler<CounterState, CounterActions> {
|
|
43
|
+
constructor([startCount = 0]) {
|
|
44
|
+
super({ initialState: { count: startCount } });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getActions() {
|
|
48
|
+
return {
|
|
49
|
+
increase() {
|
|
50
|
+
this.setState({
|
|
51
|
+
count: this.getState() + 1
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
decrease() {
|
|
55
|
+
const currentState = this.getState();
|
|
56
|
+
|
|
57
|
+
if (currentState.count > 0) {
|
|
58
|
+
this.setState({
|
|
59
|
+
count: currentState - 1
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function CounterStateFactory(...args) {
|
|
68
|
+
return new CounterStateHandler(...args);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This can be used in our factory hook function:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useStateFactory } from '@veams/status-quo';
|
|
76
|
+
import { CounterStateFactory } from './counter.state.js';
|
|
77
|
+
|
|
78
|
+
const Counter = () => {
|
|
79
|
+
const [state, actions] = useStateFactory(CounterStateFactory, [0]);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<h2>Counter: {state}</h2>
|
|
84
|
+
<button onClick={actions.increase}>Increase</button>
|
|
85
|
+
<button onClick={actions.decrease}>Decrease</button>
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**What about singletons?**
|
|
92
|
+
|
|
93
|
+
Therefore, you can use a simple singleton class or use `makeStateSingleton()` and pass it later on to the singleton hook function:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { makeStateSingleton } from '@veams/status-quo';
|
|
97
|
+
|
|
98
|
+
import { CounterStateHandler } from './counter.state.js';
|
|
99
|
+
|
|
100
|
+
export const CounterStateManager = makeStateSingleton(() => new CounterStateHandler([0]))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { useStateSingleton } from '@veams/status-quo';
|
|
105
|
+
import { CounterStateManager } from './counter.singleton.js';
|
|
106
|
+
|
|
107
|
+
const GlobalCounterHandler = () => {
|
|
108
|
+
const [_, actions] = useStateSingleton(CounterStateManager);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div>
|
|
112
|
+
<button onClick={actions.increase}>Increase</button>
|
|
113
|
+
<button onClick={actions.decrease}>Decrease</button>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const GlobalCounterDisplay = () => {
|
|
119
|
+
const [state] = useStateSingleton(CounterStateManager);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div>
|
|
123
|
+
<h2>Counter: {state}</h2>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useStateSubscription } from './state-subscription.js';
|
|
3
|
+
export function useStateFactory(stateFactoryFunction, params = []) {
|
|
4
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5
|
+
const stateHandler = useMemo(() => stateFactoryFunction(...params), params);
|
|
6
|
+
const actions = useMemo(() => stateHandler.getActions(), [stateHandler]);
|
|
7
|
+
const state = useStateSubscription(stateHandler);
|
|
8
|
+
return [state, actions];
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=state-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-factory.js","sourceRoot":"","sources":["../../src/hooks/state-factory.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D,MAAM,UAAU,eAAe,CAC7B,oBAAoE,EACpE,SAAY,EAAkB;IAE9B,uDAAuD;IACvD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,GAAG,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAEjD,OAAO,CAAC,KAAK,EAAE,OAAO,CAAW,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useStateSubscription } from './state-subscription.js';
|
|
3
|
+
export function useStateSingleton(stateSingleton) {
|
|
4
|
+
const stateHandler = useMemo(() => stateSingleton.getInstance(), [stateSingleton]);
|
|
5
|
+
const actions = useMemo(() => stateHandler.getActions(), [stateHandler]);
|
|
6
|
+
const state = useStateSubscription(stateHandler);
|
|
7
|
+
return [state, actions];
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=state-singleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-singleton.js","sourceRoot":"","sources":["../../src/hooks/state-singleton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D,MAAM,UAAU,iBAAiB,CAAO,cAAoC;IAC1E,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,WAAW,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEzE,MAAM,KAAK,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAEjD,OAAO,CAAC,KAAK,EAAE,OAAO,CAAW,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useStateSubscription(stateSubscriptionHandler) {
|
|
3
|
+
const [state, setSubscriptionState] = useState(stateSubscriptionHandler.getInitialState());
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const state$ = stateSubscriptionHandler.getObservable().subscribe((data) => {
|
|
6
|
+
setSubscriptionState(data);
|
|
7
|
+
});
|
|
8
|
+
return () => {
|
|
9
|
+
state$.unsubscribe();
|
|
10
|
+
return stateSubscriptionHandler.destroy();
|
|
11
|
+
};
|
|
12
|
+
}, [stateSubscriptionHandler]);
|
|
13
|
+
return state;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=state-subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-subscription.js","sourceRoot":"","sources":["../../src/hooks/state-subscription.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAK5C,MAAM,UAAU,oBAAoB,CAClC,wBAAwD;IAExD,MAAM,CAAC,KAAK,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CAC5C,wBAAwB,CAAC,eAAe,EAAE,CAC3C,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,wBAAwB,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;YACzE,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO,wBAAwB,CAAC,OAAO,EAAE,CAAC;QAC5C,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAE/B,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { useStateFactory, useStateSingleton } from './hooks/index.js';
|
|
2
|
+
import { makeStateSingleton, StateHandler } from './store/index.js';
|
|
3
|
+
import type { StateSingleton } from './store/index.js';
|
|
4
|
+
import type { StateSubscriptionHandler } from './types/types.js';
|
|
5
|
+
export { makeStateSingleton, StateHandler, useStateFactory, useStateSingleton };
|
|
6
|
+
export type { StateSingleton, StateSubscriptionHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAKpE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { lastValueFrom, Subject, take } from 'rxjs';
|
|
2
|
+
import { StateHandler } from '../state-handler.js';
|
|
3
|
+
class TestStateHandler extends StateHandler {
|
|
4
|
+
constructor(withDevTools) {
|
|
5
|
+
super({
|
|
6
|
+
initialState: {
|
|
7
|
+
test: 'testValue',
|
|
8
|
+
test2: 'testValue2',
|
|
9
|
+
},
|
|
10
|
+
...(withDevTools && {
|
|
11
|
+
devTools: {
|
|
12
|
+
enabled: true,
|
|
13
|
+
namespace: 'TestStateHandler',
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
getObservable() {
|
|
19
|
+
return this.getStateAsObservable();
|
|
20
|
+
}
|
|
21
|
+
getActions() {
|
|
22
|
+
return {
|
|
23
|
+
testAction: () => {
|
|
24
|
+
this.setState({ test: 'newValue' });
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
describe('State Handler', () => {
|
|
30
|
+
let stateHandler;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
stateHandler = new TestStateHandler();
|
|
33
|
+
});
|
|
34
|
+
it('should provide initial state', () => {
|
|
35
|
+
expect(stateHandler.getInitialState()).toStrictEqual({
|
|
36
|
+
test: 'testValue',
|
|
37
|
+
test2: 'testValue2',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('should provide current state', () => {
|
|
41
|
+
expect(stateHandler.getState()).toStrictEqual({
|
|
42
|
+
test: 'testValue',
|
|
43
|
+
test2: 'testValue2',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it('should support state changing via setter and merge state object on first level', async () => {
|
|
47
|
+
const expected = {
|
|
48
|
+
test: 'change',
|
|
49
|
+
test2: 'testValue2',
|
|
50
|
+
};
|
|
51
|
+
stateHandler.setState(expected);
|
|
52
|
+
const state = await lastValueFrom(stateHandler.getObservable().pipe(take(1)));
|
|
53
|
+
expect(state).toStrictEqual(expected);
|
|
54
|
+
expect(stateHandler.getState()).toStrictEqual(expected);
|
|
55
|
+
});
|
|
56
|
+
it('should support additional subscriptions handling', () => {
|
|
57
|
+
const customSubject = new Subject();
|
|
58
|
+
const spy = jest.fn();
|
|
59
|
+
const subscription = customSubject.subscribe(spy);
|
|
60
|
+
stateHandler.subscriptions = [subscription];
|
|
61
|
+
customSubject.next(1);
|
|
62
|
+
stateHandler.destroy();
|
|
63
|
+
customSubject.next(2);
|
|
64
|
+
customSubject.next(3);
|
|
65
|
+
expect(spy).toBeCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
it('should only call subscriber when object state has changed', async () => {
|
|
68
|
+
const spy = jest.fn();
|
|
69
|
+
stateHandler.getObservable().subscribe(spy);
|
|
70
|
+
stateHandler.setState({
|
|
71
|
+
test: 'test',
|
|
72
|
+
});
|
|
73
|
+
stateHandler.setState({
|
|
74
|
+
test: 'test2',
|
|
75
|
+
});
|
|
76
|
+
stateHandler.setState({
|
|
77
|
+
test: 'test2',
|
|
78
|
+
});
|
|
79
|
+
stateHandler.setState({
|
|
80
|
+
test: 'test2',
|
|
81
|
+
});
|
|
82
|
+
expect(spy).toBeCalledTimes(3); // 1. testValue (Initial value), 2. test (first setter), 3. test2 (second setter)
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=state-handler.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-handler.spec.js","sourceRoot":"","sources":["../../../src/store/__tests__/state-handler.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
__REDUX_DEVTOOLS_EXTENSION__?: {
|
|
4
|
+
connect: (opts: Record<string, unknown>) => DevTools;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export type MessagePayload = {
|
|
9
|
+
type: string;
|
|
10
|
+
payload: {
|
|
11
|
+
type: string;
|
|
12
|
+
actionId: number;
|
|
13
|
+
};
|
|
14
|
+
state: string;
|
|
15
|
+
id: string;
|
|
16
|
+
source: '@devtools-extension';
|
|
17
|
+
};
|
|
18
|
+
export type DevTools = {
|
|
19
|
+
init: (state: unknown) => void;
|
|
20
|
+
send: (action: string, state: unknown) => void;
|
|
21
|
+
subscribe: (cb: (message: MessagePayload) => void) => void;
|
|
22
|
+
};
|
|
23
|
+
export declare function withDevTools<S>(initialState: S, options?: {}): DevTools | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function withDevTools(initialState, options = {}) {
|
|
2
|
+
if (typeof window === 'undefined') {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
6
|
+
if (!window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
7
|
+
console.error('Status Quo :: Devtools Extension is not installed!');
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
11
|
+
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect(options);
|
|
12
|
+
devTools.init(initialState);
|
|
13
|
+
// eslint-disable-next-line consistent-return
|
|
14
|
+
return devTools;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=dev-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-tools.js","sourceRoot":"","sources":["../../src/store/dev-tools.ts"],"names":[],"mappings":"AAyBA,MAAM,UAAU,YAAY,CAAI,YAAe,EAAE,OAAO,GAAG,EAAE;IAC3D,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gDAAgD;IAChD,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gDAAgD;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,4BAA4B,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtE,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAE5B,6CAA6C;IAC7C,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BehaviorSubject } from 'rxjs';
|
|
2
|
+
import type { StateSubscriptionHandler } from '../types/types.js';
|
|
3
|
+
import type { Observable, Subscription } from 'rxjs';
|
|
4
|
+
type Subscriptions = Subscription[];
|
|
5
|
+
type StateHandlerProps<S> = {
|
|
6
|
+
initialState: S;
|
|
7
|
+
options?: {
|
|
8
|
+
devTools: {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
namespace: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
type StateObservableOptions = {
|
|
15
|
+
useDistinctUntilChanged?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare abstract class StateHandler<S, A> implements StateSubscriptionHandler<S, A> {
|
|
18
|
+
private readonly updates$;
|
|
19
|
+
private readonly state$;
|
|
20
|
+
private readonly initialState;
|
|
21
|
+
private devTools;
|
|
22
|
+
subscriptions: Subscriptions;
|
|
23
|
+
protected constructor({ initialState, options }: StateHandlerProps<S>);
|
|
24
|
+
getInitialState(): S;
|
|
25
|
+
getState(): S;
|
|
26
|
+
setState(newState: Partial<S>, actionName?: string): void;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
getStateItemAsObservable(key: keyof S): Observable<S[keyof S]>;
|
|
29
|
+
getStateAsObservable(options?: StateObservableOptions): BehaviorSubject<S>;
|
|
30
|
+
getObservableItem(key: keyof S): Observable<S[keyof S]>;
|
|
31
|
+
private bindUpdatesAndEvents;
|
|
32
|
+
private handleDevToolsEvents;
|
|
33
|
+
getObservable(): Observable<S>;
|
|
34
|
+
abstract getActions(): A;
|
|
35
|
+
}
|
|
36
|
+
export {};
|