@veams/status-quo 1.5.0 → 1.6.0

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 (126) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/.turbo/turbo-check$colon$types.log +4 -0
  3. package/.turbo/turbo-docs$colon$build.log +14 -0
  4. package/.turbo/turbo-lint.log +8 -0
  5. package/.turbo/turbo-test.log +15 -0
  6. package/CHANGELOG.md +24 -3
  7. package/README.md +217 -41
  8. package/dist/config/status-quo-config.d.ts +13 -0
  9. package/dist/config/status-quo-config.js +14 -0
  10. package/dist/config/status-quo-config.js.map +1 -1
  11. package/dist/hooks/__tests__/state-provider.spec.d.ts +4 -0
  12. package/dist/hooks/__tests__/state-provider.spec.js +179 -0
  13. package/dist/hooks/__tests__/state-provider.spec.js.map +1 -0
  14. package/dist/hooks/__tests__/state-selector.spec.js +11 -12
  15. package/dist/hooks/__tests__/state-selector.spec.js.map +1 -1
  16. package/dist/hooks/__tests__/state-singleton.spec.js +10 -11
  17. package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -1
  18. package/dist/hooks/index.d.ts +1 -0
  19. package/dist/hooks/index.js +1 -0
  20. package/dist/hooks/index.js.map +1 -1
  21. package/dist/hooks/state-factory.js.map +1 -1
  22. package/dist/hooks/state-provider.d.ts +14 -0
  23. package/dist/hooks/state-provider.js +24 -0
  24. package/dist/hooks/state-provider.js.map +1 -0
  25. package/dist/hooks/state-subscription-selector.js +6 -2
  26. package/dist/hooks/state-subscription-selector.js.map +1 -1
  27. package/dist/hooks/state-subscription.js +1 -1
  28. package/dist/hooks/state-subscription.js.map +1 -1
  29. package/dist/index.d.ts +4 -5
  30. package/dist/index.js +2 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/hooks/__tests__/state-provider.spec.d.ts +4 -0
  33. package/dist/react/hooks/__tests__/state-provider.spec.js +179 -0
  34. package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -0
  35. package/dist/react/hooks/__tests__/state-selector.spec.d.ts +4 -0
  36. package/dist/react/hooks/__tests__/state-selector.spec.js +499 -0
  37. package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -0
  38. package/dist/react/hooks/__tests__/state-singleton.spec.d.ts +4 -0
  39. package/dist/react/hooks/__tests__/state-singleton.spec.js +96 -0
  40. package/dist/react/hooks/__tests__/state-singleton.spec.js.map +1 -0
  41. package/{src/hooks/index.ts → dist/react/hooks/index.d.ts} +1 -0
  42. package/dist/react/hooks/index.js +7 -0
  43. package/dist/react/hooks/index.js.map +1 -0
  44. package/dist/react/hooks/state-actions.d.ts +2 -0
  45. package/dist/react/hooks/state-actions.js +5 -0
  46. package/dist/react/hooks/state-actions.js.map +1 -0
  47. package/dist/react/hooks/state-factory.d.ts +7 -0
  48. package/dist/react/hooks/state-factory.js +13 -0
  49. package/dist/react/hooks/state-factory.js.map +1 -0
  50. package/dist/react/hooks/state-handler.d.ts +2 -0
  51. package/dist/react/hooks/state-handler.js +9 -0
  52. package/dist/react/hooks/state-handler.js.map +1 -0
  53. package/dist/react/hooks/state-provider.d.ts +14 -0
  54. package/dist/react/hooks/state-provider.js +24 -0
  55. package/dist/react/hooks/state-provider.js.map +1 -0
  56. package/dist/react/hooks/state-singleton.d.ts +6 -0
  57. package/dist/react/hooks/state-singleton.js +7 -0
  58. package/dist/react/hooks/state-singleton.js.map +1 -0
  59. package/dist/react/hooks/state-subscription-selector.d.ts +3 -0
  60. package/dist/react/hooks/state-subscription-selector.js +63 -0
  61. package/dist/react/hooks/state-subscription-selector.js.map +1 -0
  62. package/dist/react/hooks/state-subscription.d.ts +9 -0
  63. package/dist/react/hooks/state-subscription.js +53 -0
  64. package/dist/react/hooks/state-subscription.js.map +1 -0
  65. package/dist/react/index.d.ts +1 -0
  66. package/dist/react/index.js +2 -0
  67. package/dist/react/index.js.map +1 -0
  68. package/dist/store/__tests__/observable-state-handler.spec.js +66 -11
  69. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
  70. package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
  71. package/dist/store/base-state-handler.d.ts +4 -6
  72. package/dist/store/base-state-handler.js +10 -9
  73. package/dist/store/base-state-handler.js.map +1 -1
  74. package/dist/store/dev-tools.js +0 -3
  75. package/dist/store/dev-tools.js.map +1 -1
  76. package/dist/store/observable-state-handler.d.ts +4 -10
  77. package/dist/store/observable-state-handler.js +4 -11
  78. package/dist/store/observable-state-handler.js.map +1 -1
  79. package/dist/store/signal-state-handler.d.ts +2 -5
  80. package/dist/store/signal-state-handler.js +3 -2
  81. package/dist/store/signal-state-handler.js.map +1 -1
  82. package/dist/store/state-singleton.js +1 -1
  83. package/dist/store/state-singleton.js.map +1 -1
  84. package/eslint.config.mjs +75 -0
  85. package/package.json +18 -18
  86. package/src/config/status-quo-config.ts +31 -1
  87. package/src/index.ts +11 -15
  88. package/src/react/hooks/__tests__/state-provider.spec.tsx +286 -0
  89. package/src/{hooks → react/hooks}/__tests__/state-selector.spec.tsx +52 -44
  90. package/src/{hooks → react/hooks}/__tests__/state-singleton.spec.tsx +21 -20
  91. package/src/react/hooks/index.ts +11 -0
  92. package/src/{hooks → react/hooks}/state-actions.tsx +1 -1
  93. package/src/{hooks → react/hooks}/state-factory.tsx +2 -2
  94. package/src/{hooks → react/hooks}/state-handler.tsx +1 -1
  95. package/src/react/hooks/state-provider.tsx +56 -0
  96. package/src/{hooks → react/hooks}/state-singleton.tsx +1 -1
  97. package/src/{hooks → react/hooks}/state-subscription-selector.tsx +15 -6
  98. package/src/{hooks → react/hooks}/state-subscription.tsx +5 -9
  99. package/src/react/index.ts +1 -0
  100. package/src/store/__tests__/observable-state-handler.spec.ts +92 -13
  101. package/src/store/__tests__/signal-state-handler.spec.ts +5 -1
  102. package/src/store/base-state-handler.ts +17 -24
  103. package/src/store/dev-tools.ts +3 -3
  104. package/src/store/observable-state-handler.ts +12 -22
  105. package/src/store/signal-state-handler.ts +11 -8
  106. package/src/store/state-singleton.ts +1 -1
  107. package/tsconfig.json +2 -3
  108. package/.eslintrc.cjs +0 -132
  109. package/.github/workflows/pages.yml +0 -46
  110. package/.github/workflows/release.yml +0 -33
  111. package/.nvmrc +0 -1
  112. package/.prettierrc +0 -7
  113. package/docs/assets/index-BBmpszOW.css +0 -1
  114. package/docs/assets/index-Cf8El_RO.js +0 -194
  115. package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
  116. package/docs/index.html +0 -13
  117. package/playground/index.html +0 -12
  118. package/playground/src/App.tsx +0 -699
  119. package/playground/src/assets/philosophy-agnostic.svg +0 -18
  120. package/playground/src/assets/philosophy-separation.svg +0 -13
  121. package/playground/src/assets/philosophy-swap.svg +0 -17
  122. package/playground/src/assets/statusquo-logo.png +0 -0
  123. package/playground/src/main.tsx +0 -19
  124. package/playground/src/styles.css +0 -534
  125. package/playground/tsconfig.json +0 -12
  126. package/playground/vite.config.ts +0 -18
@@ -1,6 +1,6 @@
1
1
  import { BehaviorSubject, distinctUntilChanged, distinctUntilKeyChanged, map } from 'rxjs';
2
- import { BaseStateHandler } from './base-state-handler.js';
3
2
  import { resolveDistinctOptions } from '../config/status-quo-config.js';
3
+ import { BaseStateHandler } from './base-state-handler.js';
4
4
  export class ObservableStateHandler extends BaseStateHandler {
5
5
  state$;
6
6
  distinctOptions;
@@ -16,10 +16,10 @@ export class ObservableStateHandler extends BaseStateHandler {
16
16
  setStateValue(nextState) {
17
17
  this.state$.next(nextState);
18
18
  }
19
- getStateItemAsObservable(key) {
19
+ getObservableItem(key) {
20
20
  return this.state$.pipe(distinctUntilKeyChanged(key), map((state) => state[key]));
21
21
  }
22
- getStateAsObservable(options = {}) {
22
+ getObservable(options = {}) {
23
23
  const useDistinctUntilChanged = options.useDistinctUntilChanged ?? this.distinctOptions.enabled;
24
24
  if (!useDistinctUntilChanged) {
25
25
  return this.state$;
@@ -28,15 +28,8 @@ export class ObservableStateHandler extends BaseStateHandler {
28
28
  return this.distinctOptions.comparator(previous, next);
29
29
  }));
30
30
  }
31
- getObservable(key) {
32
- return this.getStateItemAsObservable(key);
33
- }
34
- /** @deprecated Use getObservable instead. */
35
- getObservableItem(key) {
36
- return this.getObservable(key);
37
- }
38
31
  subscribe(listener) {
39
- const subscription = this.getStateAsObservable().subscribe((nextState) => {
32
+ const subscription = this.getObservable().subscribe((nextState) => {
40
33
  listener(nextState);
41
34
  });
42
35
  return () => subscription.unsubscribe();
@@ -1 +1 @@
1
- {"version":3,"file":"observable-state-handler.js","sourceRoot":"","sources":["../../src/store/observable-state-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAE3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAmBxE,MAAM,OAAgB,sBAA6B,SAAQ,gBAAsB;IAC9D,MAAM,CAAqB;IAC3B,eAAe,CAA+C;IAE/E,YAAsB,EAAE,YAAY,EAAE,OAAO,EAAkC;QAC7E,KAAK,CAAC,YAAY,CAAC,CAAC;QACpB,IAAI,CAAC,MAAM,GAAG,IAAI,eAAe,CAAI,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,GAAG,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;QACnG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAES,aAAa;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAES,aAAa,CAAC,SAAY;QAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;IAED,wBAAwB,CAAC,GAAY;QACnC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CACrB,uBAAuB,CAAC,GAAG,CAAC,EAC5B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAC3B,CAAC;IACJ,CAAC;IAED,oBAAoB,CAAC,UAAkC,EAAE;QACvD,MAAM,uBAAuB,GAC3B,OAAO,CAAC,uBAAuB,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAElE,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CACrB,oBAAoB,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE;YACtC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CACc,CAAC;IACrB,CAAC;IAED,aAAa,CAAC,GAAY;QACxB,OAAO,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC;IAED,6CAA6C;IAC7C,iBAAiB,CAAC,GAAY;QAC5B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAID,SAAS,CAAC,QAA4B;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE;YACvE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;IAC1C,CAAC;CACF"}
1
+ {"version":3,"file":"observable-state-handler.js","sourceRoot":"","sources":["../../src/store/observable-state-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAE3F,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAgB3D,MAAM,OAAgB,sBAA6B,SAAQ,gBAAsB;IAC9D,MAAM,CAAqB;IAC3B,eAAe,CAA+C;IAE/E,YAAsB,EAAE,YAAY,EAAE,OAAO,EAAkC;QAC7E,KAAK,CAAC,YAAY,CAAC,CAAC;QACpB,IAAI,CAAC,MAAM,GAAG,IAAI,eAAe,CAAI,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,GAAG,sBAAsB,CAC3C,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,uBAAuB,CACjC,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAES,aAAa;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;IAES,aAAa,CAAC,SAAY;QAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;IAED,iBAAiB,CAAoB,GAAM;QACzC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CACrB,uBAAuB,CAAC,GAAG,CAAC,EAC5B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAC3B,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,UAAkC,EAAE;QAChD,MAAM,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAEhG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CACrB,oBAAoB,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE;YACtC,OAAO,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAID,SAAS,CAAC,QAA4B;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE;YAChE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;IAC1C,CAAC;CACF"}
@@ -1,13 +1,10 @@
1
1
  import { BaseStateHandler } from './base-state-handler.js';
2
+ import type { DevToolsOptions, DistinctOptions } from '../config/status-quo-config.js';
2
3
  import type { Signal } from '@preact/signals-core';
3
- import type { DistinctOptions } from '../config/status-quo-config.js';
4
4
  type SignalStateHandlerProps<S> = {
5
5
  initialState: S;
6
6
  options?: {
7
- devTools?: {
8
- enabled?: boolean;
9
- namespace: string;
10
- };
7
+ devTools?: DevToolsOptions;
11
8
  distinct?: DistinctOptions<S>;
12
9
  useDistinctUntilChanged?: boolean;
13
10
  };
@@ -1,6 +1,6 @@
1
1
  import { signal } from '@preact/signals-core';
2
- import { BaseStateHandler } from './base-state-handler.js';
3
2
  import { resolveDistinctOptions } from '../config/status-quo-config.js';
3
+ import { BaseStateHandler } from './base-state-handler.js';
4
4
  export class SignalStateHandler extends BaseStateHandler {
5
5
  state;
6
6
  distinctOptions;
@@ -23,7 +23,8 @@ export class SignalStateHandler extends BaseStateHandler {
23
23
  listener(nextState);
24
24
  return;
25
25
  }
26
- if (this.distinctOptions.enabled && this.distinctOptions.comparator(previousSnapshot, nextState)) {
26
+ if (this.distinctOptions.enabled &&
27
+ this.distinctOptions.comparator(previousSnapshot, nextState)) {
27
28
  previousSnapshot = nextState;
28
29
  return;
29
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"signal-state-handler.js","sourceRoot":"","sources":["../../src/store/signal-state-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAiBxE,MAAM,OAAgB,kBAAyB,SAAQ,gBAAsB;IAC1D,KAAK,CAAY;IACjB,eAAe,CAA+C;IAE/E,YAAsB,EAAE,YAAY,EAAE,OAAO,EAA8B;QACzE,KAAK,CAAC,YAAY,CAAC,CAAC;QAEpB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;QACnG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAID,SAAS,CAAC,QAA4B;QACpC,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,IAAI,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAExC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE;YACxC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,IAAI,CAAC;gBACnB,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACpB,OAAO;YACT,CAAC;YAED,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,IAAI,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAAE,CAAC;gBACjG,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,OAAO;YACT,CAAC;YAED,gBAAgB,GAAG,SAAS,CAAC;YAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAES,aAAa;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IAES,aAAa,CAAC,SAAY;QAClC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;IAC/B,CAAC;CACF"}
1
+ {"version":3,"file":"signal-state-handler.js","sourceRoot":"","sources":["../../src/store/signal-state-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAc3D,MAAM,OAAgB,kBAAyB,SAAQ,gBAAsB;IAC1D,KAAK,CAAY;IACjB,eAAe,CAA+C;IAE/E,YAAsB,EAAE,YAAY,EAAE,OAAO,EAA8B;QACzE,KAAK,CAAC,YAAY,CAAC,CAAC;QAEpB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAI,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,sBAAsB,CAC3C,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,uBAAuB,CACjC,CAAC;QACF,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAID,SAAS,CAAC,QAA4B;QACpC,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,IAAI,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAExC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE;YACxC,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,IAAI,CAAC;gBACnB,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;gBACpB,OAAO;YACT,CAAC;YAED,IACE,IAAI,CAAC,eAAe,CAAC,OAAO;gBAC5B,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAC5D,CAAC;gBACD,gBAAgB,GAAG,SAAS,CAAC;gBAC7B,OAAO;YACT,CAAC;YAED,gBAAgB,GAAG,SAAS,CAAC;YAC7B,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC;IAES,aAAa;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1B,CAAC;IAES,aAAa,CAAC,SAAY;QAClC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;IAC/B,CAAC;CACF"}
@@ -1,4 +1,4 @@
1
- export function makeStateSingleton(stateHandlerFactory, { destroyOnNoConsumers = true } = {}) {
1
+ export function makeStateSingleton(stateHandlerFactory, { destroyOnNoConsumers = false } = {}) {
2
2
  let instance = null;
3
3
  const singleton = {
4
4
  destroyOnNoConsumers,
@@ -1 +1 @@
1
- {"version":3,"file":"state-singleton.js","sourceRoot":"","sources":["../../src/store/state-singleton.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,kBAAkB,CAChC,mBAAyD,EACzD,EAAE,oBAAoB,GAAG,IAAI,KAA4B,EAAE;IAE3D,IAAI,QAAQ,GAA0C,IAAI,CAAC;IAC3D,MAAM,SAAS,GAGX;QACF,oBAAoB;QACpB,WAAW;YACT,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,mBAAmB,EAAE,CAAC;YACnC,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,eAAe;YACb,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;KACF,CAAC;IAEF,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"state-singleton.js","sourceRoot":"","sources":["../../src/store/state-singleton.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,kBAAkB,CAChC,mBAAyD,EACzD,EAAE,oBAAoB,GAAG,KAAK,KAA4B,EAAE;IAE5D,IAAI,QAAQ,GAA0C,IAAI,CAAC;IAC3D,MAAM,SAAS,GAGX;QACF,oBAAoB;QACpB,WAAW;YACT,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,mBAAmB,EAAE,CAAC;YACnC,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,eAAe;YACb,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;KACF,CAAC;IAEF,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ import js from '@eslint/js';
4
+ import { defineConfig } from 'eslint/config';
5
+ import reactPlugin from 'eslint-plugin-react';
6
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
7
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
8
+ import globals from 'globals';
9
+ import tseslint from 'typescript-eslint';
10
+
11
+ const packageRoot = fileURLToPath(new URL('.', import.meta.url));
12
+ const sourceFiles = ['src/**/*.{ts,tsx}'];
13
+ const testFiles = ['src/**/*.{spec,test}.{ts,tsx}', 'src/**/__tests__/**/*.{ts,tsx}'];
14
+
15
+ export default defineConfig(
16
+ {
17
+ ignores: ['dist/**', 'coverage/**'],
18
+ },
19
+ js.configs.recommended,
20
+ tseslint.configs.recommended,
21
+ tseslint.configs.recommendedTypeChecked,
22
+ {
23
+ files: sourceFiles,
24
+ languageOptions: {
25
+ globals: {
26
+ ...globals.browser,
27
+ ...globals.node,
28
+ },
29
+ parserOptions: {
30
+ ecmaFeatures: {
31
+ jsx: true,
32
+ },
33
+ projectService: true,
34
+ tsconfigRootDir: packageRoot,
35
+ },
36
+ sourceType: 'module',
37
+ },
38
+ plugins: {
39
+ react: reactPlugin,
40
+ 'react-hooks': reactHooksPlugin,
41
+ 'jsx-a11y': jsxA11yPlugin,
42
+ },
43
+ settings: {
44
+ react: {
45
+ version: 'detect',
46
+ },
47
+ },
48
+ rules: {
49
+ ...reactPlugin.configs.flat.recommended.rules,
50
+ ...reactPlugin.configs.flat['jsx-runtime'].rules,
51
+ ...jsxA11yPlugin.flatConfigs.recommended.rules,
52
+ ...reactHooksPlugin.configs.recommended.rules,
53
+ '@typescript-eslint/consistent-type-imports': 'error',
54
+ '@typescript-eslint/no-unused-vars': [
55
+ 'error',
56
+ {
57
+ argsIgnorePattern: '^_',
58
+ varsIgnorePattern: '^_',
59
+ },
60
+ ],
61
+ 'react/prop-types': 'off',
62
+ },
63
+ },
64
+ {
65
+ files: testFiles,
66
+ languageOptions: {
67
+ globals: {
68
+ ...globals.jest,
69
+ },
70
+ },
71
+ rules: {
72
+ 'react/display-name': 'off',
73
+ },
74
+ }
75
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veams/status-quo",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "The manager to rule states in frontend.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -12,12 +12,12 @@
12
12
  },
13
13
  "require": "./dist/index.js"
14
14
  },
15
- "./hooks": {
15
+ "./react": {
16
16
  "import": {
17
- "types": "./dist/hooks/index.d.ts",
18
- "default": "./dist/hooks/index.js"
17
+ "types": "./dist/react/index.d.ts",
18
+ "default": "./dist/react/index.js"
19
19
  },
20
- "require": "./dist/hooks/index.js"
20
+ "require": "./dist/react/index.js"
21
21
  },
22
22
  "./store": {
23
23
  "import": {
@@ -37,12 +37,9 @@
37
37
  "compile": "npm-run-all bundle:ts",
38
38
  "dev": "npm-run-all --parallel watch:**",
39
39
  "generate:types": "tsc --project tsconfig.json --emitDeclarationOnly true --declaration true --declarationDir ./dist/types",
40
- "lint": "pnpm run lint:ts",
41
- "lint:ts": "eslint --fix \"src/**/*.{tsx,ts}\" --config ./.eslintrc.cjs",
40
+ "lint": "npm run lint:ts",
41
+ "lint:ts": "eslint --fix \"src/**/*.{tsx,ts}\"",
42
42
  "start": "npm-run-all --parallel watch:**",
43
- "playground": "vite --config playground/vite.config.ts",
44
- "docs:build": "vite build --config playground/vite.config.ts",
45
- "docs:preview": "vite preview --config playground/vite.config.ts",
46
43
  "watch:bundle:ts": "npm run bundle:ts -- -w",
47
44
  "watch:generate:types": "npm run generate:types -- --watch",
48
45
  "test": "cross-env NODE_ENV=test jest --config jest.config.cjs",
@@ -56,25 +53,20 @@
56
53
  },
57
54
  "devDependencies": {
58
55
  "@preact/signals-core": "^1.13.0",
59
- "@types/prismjs": "^1.26.4",
60
56
  "@types/react-dom": "^19.0.0",
61
57
  "@types/jest": "30.0.0",
62
58
  "@types/node": "25.2.3",
63
59
  "@types/react": "19.2.14",
64
- "@vitejs/plugin-react": "^5.0.0",
65
60
  "cross-env": "10.1.0",
66
- "eslint": "8.55.0",
67
- "jest": "30.2.0",
68
- "jest-environment-jsdom": "30.2.0",
61
+ "jest": "30.3.0",
62
+ "jest-environment-jsdom": "30.3.0",
69
63
  "npm-run-all": "4.1.5",
70
64
  "prettier": "3.8.1",
71
- "prismjs": "^1.29.0",
72
65
  "react": "19.2.4",
73
66
  "react-dom": "19.2.4",
74
67
  "rxjs": "7.8.2",
75
68
  "tslib": "2.8.1",
76
69
  "typescript": "5.9.3",
77
- "vite": "^5.0.0",
78
70
  "@swc/jest": "0.2.39",
79
71
  "@swc/core": "1.15.11",
80
72
  "release-it": "19.2.4"
@@ -86,5 +78,13 @@
86
78
  "access": "public"
87
79
  },
88
80
  "author": "Sebastian Fitzner",
89
- "license": "MIT"
81
+ "license": "MIT",
82
+ "repository": {
83
+ "type": "git",
84
+ "url": "git+https://github.com/Veams/veams.git",
85
+ "directory": "packages/status-quo"
86
+ },
87
+ "bugs": {
88
+ "url": "https://github.com/Veams/veams/issues"
89
+ }
90
90
  }
@@ -5,7 +5,17 @@ export type DistinctOptions<T = unknown> = {
5
5
  comparator?: DistinctComparator<T>;
6
6
  };
7
7
 
8
+ export type DevToolsOptions = {
9
+ enabled?: boolean;
10
+ namespace?: string;
11
+ };
12
+
13
+ export type GlobalDevToolsOptions = {
14
+ enabled?: boolean;
15
+ };
16
+
8
17
  export type StatusQuoConfig<T = unknown> = {
18
+ devTools?: GlobalDevToolsOptions;
9
19
  distinct?: DistinctOptions<T>;
10
20
  };
11
21
 
@@ -14,7 +24,12 @@ type ResolvedDistinctOptions<T = unknown> = {
14
24
  comparator: DistinctComparator<T>;
15
25
  };
16
26
 
27
+ type ResolvedDevToolsOptions = {
28
+ enabled: boolean;
29
+ };
30
+
17
31
  type ResolvedStatusQuoConfig = {
32
+ devTools: ResolvedDevToolsOptions;
18
33
  distinct: ResolvedDistinctOptions;
19
34
  };
20
35
 
@@ -32,6 +47,9 @@ function distinctAsJson(previous: unknown, next: unknown) {
32
47
 
33
48
  function createDefaultStatusQuoConfig(): ResolvedStatusQuoConfig {
34
49
  return {
50
+ devTools: {
51
+ enabled: false,
52
+ },
35
53
  distinct: {
36
54
  enabled: true,
37
55
  comparator: distinctAsJson,
@@ -43,6 +61,9 @@ let statusQuoConfig = createDefaultStatusQuoConfig();
43
61
 
44
62
  export function setupStatusQuo<T = unknown>(config: StatusQuoConfig<T> = {}) {
45
63
  statusQuoConfig = {
64
+ devTools: {
65
+ enabled: config.devTools?.enabled ?? false,
66
+ },
46
67
  distinct: {
47
68
  enabled: config.distinct?.enabled ?? true,
48
69
  comparator: (config.distinct?.comparator ?? distinctAsJson) as DistinctComparator,
@@ -57,12 +78,21 @@ export function resolveDistinctOptions<T>(
57
78
  return {
58
79
  enabled: options?.enabled ?? useDistinctUntilChanged ?? statusQuoConfig.distinct.enabled,
59
80
  comparator: (options?.comparator ??
60
- statusQuoConfig.distinct.comparator) as DistinctComparator<T>,
81
+ statusQuoConfig.distinct.comparator),
82
+ };
83
+ }
84
+
85
+ export function resolveDevToolsOptions(options?: DevToolsOptions): ResolvedDevToolsOptions {
86
+ return {
87
+ enabled: options?.enabled ?? statusQuoConfig.devTools.enabled,
61
88
  };
62
89
  }
63
90
 
64
91
  export function getStatusQuoConfig() {
65
92
  return {
93
+ devTools: {
94
+ enabled: statusQuoConfig.devTools.enabled,
95
+ },
66
96
  distinct: {
67
97
  enabled: statusQuoConfig.distinct.enabled,
68
98
  comparator: statusQuoConfig.distinct.comparator,
package/src/index.ts CHANGED
@@ -1,19 +1,18 @@
1
- import {
2
- useStateActions,
3
- useStateFactory,
4
- useStateHandler,
5
- useStateSingleton,
6
- useStateSubscription,
7
- } from './hooks';
1
+ import { setupStatusQuo } from './config/status-quo-config.js';
8
2
  import {
9
3
  BaseStateHandler,
10
4
  makeStateSingleton,
11
5
  ObservableStateHandler,
12
6
  SignalStateHandler,
13
7
  } from './store';
14
- import { setupStatusQuo } from './config/status-quo-config.js';
15
8
 
16
- import type { DistinctComparator, DistinctOptions, StatusQuoConfig } from './config/status-quo-config.js';
9
+ import type {
10
+ DevToolsOptions,
11
+ GlobalDevToolsOptions,
12
+ DistinctComparator,
13
+ DistinctOptions,
14
+ StatusQuoConfig,
15
+ } from './config/status-quo-config.js';
17
16
  import type { StateSingleton, StateSingletonOptions } from './store';
18
17
  import type { StateSubscriptionHandler } from './types/types.js';
19
18
 
@@ -21,16 +20,13 @@ export {
21
20
  BaseStateHandler,
22
21
  makeStateSingleton,
23
22
  ObservableStateHandler,
24
- SignalStateHandler,
25
23
  setupStatusQuo,
26
- useStateActions,
27
- useStateFactory,
28
- useStateHandler,
29
- useStateSingleton,
30
- useStateSubscription,
24
+ SignalStateHandler,
31
25
  };
32
26
 
33
27
  export type {
28
+ DevToolsOptions,
29
+ GlobalDevToolsOptions,
34
30
  DistinctComparator,
35
31
  DistinctOptions,
36
32
  StateSingleton,
@@ -0,0 +1,286 @@
1
+ import React, { act } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ import {
5
+ StateProvider,
6
+ useProvidedStateActions,
7
+ useProvidedStateHandler,
8
+ useProvidedStateSubscription,
9
+ } from '../state-provider.js';
10
+
11
+ import type { StateSubscriptionHandler } from '../../../types/types.js';
12
+
13
+ declare global {
14
+ // React 19 requires this flag in test environments that use manual act() calls.
15
+
16
+ var IS_REACT_ACT_ENVIRONMENT: boolean;
17
+ }
18
+
19
+ type TestState = {
20
+ count: number;
21
+ label: string;
22
+ };
23
+
24
+ type TestActions = {
25
+ increment: () => void;
26
+ rename: (label: string) => void;
27
+ };
28
+
29
+ class TestStateHandler implements StateSubscriptionHandler<TestState, TestActions> {
30
+ private readonly initialState: TestState;
31
+ private state: TestState;
32
+ private readonly listeners = new Set<(value: TestState) => void>();
33
+
34
+ destroy = jest.fn();
35
+
36
+ constructor(initialState: TestState) {
37
+ this.initialState = initialState;
38
+ this.state = initialState;
39
+ }
40
+
41
+ subscribe(listener: () => void): () => void;
42
+ subscribe(listener: (value: TestState) => void): () => void;
43
+ subscribe(listener: ((value: TestState) => void) | (() => void)) {
44
+ const typedListener = listener as (value: TestState) => void;
45
+ this.listeners.add(typedListener);
46
+
47
+ return () => {
48
+ this.listeners.delete(typedListener);
49
+ };
50
+ }
51
+
52
+ getSnapshot = () => {
53
+ return this.state;
54
+ };
55
+
56
+ getInitialState = () => {
57
+ return this.initialState;
58
+ };
59
+
60
+ getActions = () => {
61
+ return {
62
+ increment: () => {
63
+ this.state = {
64
+ ...this.state,
65
+ count: this.state.count + 1,
66
+ };
67
+
68
+ this.emitStateChange();
69
+ },
70
+ rename: (label: string) => {
71
+ this.state = {
72
+ ...this.state,
73
+ label,
74
+ };
75
+
76
+ this.emitStateChange();
77
+ },
78
+ };
79
+ };
80
+
81
+ private emitStateChange() {
82
+ const nextState = this.state;
83
+ this.listeners.forEach((listener) => listener(nextState));
84
+ }
85
+ }
86
+
87
+ function CountConsumer({ onRender }: { onRender: (count: number) => void }) {
88
+ const [count] = useProvidedStateSubscription<TestState, TestActions, number>(
89
+ (state) => state.count
90
+ );
91
+
92
+ onRender(count);
93
+
94
+ return <span>{count}</span>;
95
+ }
96
+
97
+ function FullStateConsumer({
98
+ onActionsReady,
99
+ onRender,
100
+ }: {
101
+ onActionsReady: (actions: TestActions) => void;
102
+ onRender: (state: TestState) => void;
103
+ }) {
104
+ const [state, actions] = useProvidedStateSubscription<TestState, TestActions>();
105
+
106
+ onRender(state);
107
+ onActionsReady(actions);
108
+
109
+ return <span>{state.label}</span>;
110
+ }
111
+
112
+ function ActionsOnlyConsumer({
113
+ onActionsReady,
114
+ onRender,
115
+ }: {
116
+ onActionsReady: (actions: TestActions) => void;
117
+ onRender: () => void;
118
+ }) {
119
+ const actions = useProvidedStateActions<TestState, TestActions>();
120
+
121
+ onRender();
122
+ onActionsReady(actions);
123
+
124
+ return <span>actions-only</span>;
125
+ }
126
+
127
+ function HandlerConsumer({
128
+ onHandlerReady,
129
+ }: {
130
+ onHandlerReady: (handler: StateSubscriptionHandler<TestState, TestActions>) => void;
131
+ }) {
132
+ const handler = useProvidedStateHandler<TestState, TestActions>();
133
+
134
+ onHandlerReady(handler);
135
+
136
+ return <span>handler</span>;
137
+ }
138
+
139
+ function MissingProviderConsumer() {
140
+ useProvidedStateActions<TestState, TestActions>();
141
+
142
+ return null;
143
+ }
144
+
145
+ describe('StateProvider', () => {
146
+ let container: HTMLDivElement;
147
+ let root: ReturnType<typeof createRoot>;
148
+
149
+ beforeAll(() => {
150
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
151
+ });
152
+
153
+ beforeEach(() => {
154
+ container = document.createElement('div');
155
+ document.body.appendChild(container);
156
+ root = createRoot(container);
157
+ });
158
+
159
+ afterEach(() => {
160
+ act(() => {
161
+ root.unmount();
162
+ });
163
+
164
+ container.remove();
165
+ });
166
+
167
+ afterAll(() => {
168
+ globalThis.IS_REACT_ACT_ENVIRONMENT = false;
169
+ });
170
+
171
+ it('should share one handler instance across the provider subtree', () => {
172
+ const stateHandler = new TestStateHandler({
173
+ count: 0,
174
+ label: 'Counter',
175
+ });
176
+ const countRenderSpy = jest.fn<void, [number]>();
177
+ const actionsRenderSpy = jest.fn();
178
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
179
+ const handlerReadySpy = jest.fn<void, [StateSubscriptionHandler<TestState, TestActions>]>();
180
+
181
+ act(() => {
182
+ root.render(
183
+ <StateProvider instance={stateHandler}>
184
+ <CountConsumer onRender={countRenderSpy} />
185
+ <ActionsOnlyConsumer onActionsReady={actionsReadySpy} onRender={actionsRenderSpy} />
186
+ <HandlerConsumer onHandlerReady={handlerReadySpy} />
187
+ </StateProvider>
188
+ );
189
+ });
190
+
191
+ expect(countRenderSpy).toHaveBeenCalledTimes(1);
192
+ expect(countRenderSpy).toHaveBeenLastCalledWith(0);
193
+ expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
194
+ expect(handlerReadySpy).toHaveBeenCalledWith(stateHandler);
195
+
196
+ const [[actions]] = actionsReadySpy.mock.calls as [[TestActions]];
197
+
198
+ act(() => {
199
+ actions.rename('Renamed');
200
+ });
201
+
202
+ expect(countRenderSpy).toHaveBeenCalledTimes(1);
203
+ expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
204
+
205
+ act(() => {
206
+ actions.increment();
207
+ });
208
+
209
+ expect(countRenderSpy).toHaveBeenCalledTimes(2);
210
+ expect(countRenderSpy).toHaveBeenLastCalledWith(1);
211
+ expect(actionsRenderSpy).toHaveBeenCalledTimes(1);
212
+ });
213
+
214
+ it('should return the full snapshot when no selector is provided', () => {
215
+ const stateHandler = new TestStateHandler({
216
+ count: 2,
217
+ label: 'Counter',
218
+ });
219
+ const renderSpy = jest.fn<void, [TestState]>();
220
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
221
+
222
+ act(() => {
223
+ root.render(
224
+ <StateProvider instance={stateHandler}>
225
+ <FullStateConsumer onActionsReady={actionsReadySpy} onRender={renderSpy} />
226
+ </StateProvider>
227
+ );
228
+ });
229
+
230
+ expect(renderSpy).toHaveBeenCalledTimes(1);
231
+ expect(renderSpy).toHaveBeenLastCalledWith({ count: 2, label: 'Counter' });
232
+
233
+ const [[actions]] = actionsReadySpy.mock.calls as [[TestActions]];
234
+
235
+ act(() => {
236
+ actions.increment();
237
+ });
238
+
239
+ expect(renderSpy).toHaveBeenCalledTimes(2);
240
+ expect(renderSpy).toHaveBeenLastCalledWith({ count: 3, label: 'Counter' });
241
+ });
242
+
243
+ it('should follow a new instance when the provider instance changes', () => {
244
+ const firstHandler = new TestStateHandler({
245
+ count: 1,
246
+ label: 'First',
247
+ });
248
+ const secondHandler = new TestStateHandler({
249
+ count: 8,
250
+ label: 'Second',
251
+ });
252
+ const renderSpy = jest.fn<void, [number]>();
253
+
254
+ act(() => {
255
+ root.render(
256
+ <StateProvider instance={firstHandler}>
257
+ <CountConsumer onRender={renderSpy} />
258
+ </StateProvider>
259
+ );
260
+ });
261
+
262
+ act(() => {
263
+ root.render(
264
+ <StateProvider instance={secondHandler}>
265
+ <CountConsumer onRender={renderSpy} />
266
+ </StateProvider>
267
+ );
268
+ });
269
+
270
+ expect(renderSpy).toHaveBeenCalledTimes(2);
271
+ expect(renderSpy).toHaveBeenNthCalledWith(1, 1);
272
+ expect(renderSpy).toHaveBeenNthCalledWith(2, 8);
273
+ });
274
+
275
+ it('should throw when provider hooks are used outside StateProvider', () => {
276
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
277
+
278
+ expect(() => {
279
+ act(() => {
280
+ root.render(<MissingProviderConsumer />);
281
+ });
282
+ }).toThrow('No StateProvider instance found in the current React tree.');
283
+
284
+ consoleErrorSpy.mockRestore();
285
+ });
286
+ });