@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.
Files changed (52) hide show
  1. package/.eslintrc.cjs +132 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierrc +7 -0
  4. package/README.md +130 -0
  5. package/dist/hooks/index.d.ts +2 -0
  6. package/dist/hooks/index.js +3 -0
  7. package/dist/hooks/index.js.map +1 -0
  8. package/dist/hooks/state-factory.d.ts +2 -0
  9. package/dist/hooks/state-factory.js +10 -0
  10. package/dist/hooks/state-factory.js.map +1 -0
  11. package/dist/hooks/state-singleton.d.ts +2 -0
  12. package/dist/hooks/state-singleton.js +9 -0
  13. package/dist/hooks/state-singleton.js.map +1 -0
  14. package/dist/hooks/state-subscription.d.ts +3 -0
  15. package/dist/hooks/state-subscription.js +15 -0
  16. package/dist/hooks/state-subscription.js.map +1 -0
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.js +4 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/store/__tests__/state-handler.spec.d.ts +1 -0
  21. package/dist/store/__tests__/state-handler.spec.js +85 -0
  22. package/dist/store/__tests__/state-handler.spec.js.map +1 -0
  23. package/dist/store/dev-tools.d.ts +23 -0
  24. package/dist/store/dev-tools.js +16 -0
  25. package/dist/store/dev-tools.js.map +1 -0
  26. package/dist/store/index.d.ts +3 -0
  27. package/dist/store/index.js +3 -0
  28. package/dist/store/index.js.map +1 -0
  29. package/dist/store/state-handler.d.ts +36 -0
  30. package/dist/store/state-handler.js +122 -0
  31. package/dist/store/state-handler.js.map +1 -0
  32. package/dist/store/state-singleton.d.ts +5 -0
  33. package/dist/store/state-singleton.js +12 -0
  34. package/dist/store/state-singleton.js.map +1 -0
  35. package/dist/types/types.d.ts +7 -0
  36. package/dist/types/types.js +2 -0
  37. package/dist/types/types.js.map +1 -0
  38. package/jest.config.ci.cjs +7 -0
  39. package/jest.config.cjs +22 -0
  40. package/package.json +79 -0
  41. package/src/hooks/index.ts +2 -0
  42. package/src/hooks/state-factory.tsx +17 -0
  43. package/src/hooks/state-singleton.tsx +14 -0
  44. package/src/hooks/state-subscription.tsx +25 -0
  45. package/src/index.ts +9 -0
  46. package/src/store/__tests__/state-handler.spec.ts +108 -0
  47. package/src/store/dev-tools.ts +44 -0
  48. package/src/store/index.ts +3 -0
  49. package/src/store/state-handler.ts +181 -0
  50. package/src/store/state-singleton.ts +21 -0
  51. package/src/types/types.ts +8 -0
  52. 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
@@ -0,0 +1,7 @@
1
+ {
2
+ "useTabs": false,
3
+ "printWidth": 100,
4
+ "singleQuote": true,
5
+ "trailingComma": "es5",
6
+ "semi": true
7
+ }
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,2 @@
1
+ export { useStateFactory } from './state-factory.js';
2
+ export { useStateSingleton } from './state-singleton.js';
@@ -0,0 +1,3 @@
1
+ export { useStateFactory } from './state-factory.js';
2
+ export { useStateSingleton } from './state-singleton.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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,2 @@
1
+ import type { StateSubscriptionHandler } from '../types/types.js';
2
+ export declare function useStateFactory<V, A, P extends unknown[]>(stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>, params?: P): [V, A];
@@ -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,2 @@
1
+ import type { StateSingleton } from '../store/state-singleton.js';
2
+ export declare function useStateSingleton<V, A>(stateSingleton: StateSingleton<V, A>): [V, A];
@@ -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,3 @@
1
+ import type { StateSubscriptionHandler } from '../types/types.js';
2
+ import type { SetStateAction } from 'react';
3
+ export declare function useStateSubscription<V, A>(stateSubscriptionHandler: StateSubscriptionHandler<V, A>): SetStateAction<V>;
@@ -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"}
@@ -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,4 @@
1
+ import { useStateFactory, useStateSingleton } from './hooks/index.js';
2
+ import { makeStateSingleton, StateHandler } from './store/index.js';
3
+ export { makeStateSingleton, StateHandler, useStateFactory, useStateSingleton };
4
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ export { StateHandler } from './state-handler.js';
2
+ export type { StateSingleton } from './state-singleton.js';
3
+ export { makeStateSingleton } from './state-singleton.js';
@@ -0,0 +1,3 @@
1
+ export { StateHandler } from './state-handler.js';
2
+ export { makeStateSingleton } from './state-singleton.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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 {};