reactish-state 1.2.1-alpha.2 → 2.0.0-alpha.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 (45) hide show
  1. package/README.md +78 -56
  2. package/dist/cjs/index.cjs +2 -2
  3. package/dist/cjs/middleware/applyMiddleware.cjs +10 -0
  4. package/dist/cjs/middleware/immer.cjs +9 -0
  5. package/dist/cjs/middleware/persist.cjs +41 -0
  6. package/dist/{middleware/cjs → cjs/middleware}/reduxDevtools.cjs +10 -8
  7. package/dist/cjs/plugin/applyPlugin.cjs +5 -0
  8. package/dist/{plugin/cjs → cjs/plugin}/reduxDevtools.cjs +5 -4
  9. package/dist/cjs/vanilla/selector.cjs +8 -8
  10. package/dist/cjs/vanilla/state.cjs +6 -6
  11. package/dist/esm/index.mjs +2 -2
  12. package/dist/esm/middleware/applyMiddleware.mjs +8 -0
  13. package/dist/esm/middleware/immer.mjs +7 -0
  14. package/dist/esm/middleware/persist.mjs +39 -0
  15. package/dist/{middleware/esm → esm/middleware}/reduxDevtools.mjs +10 -8
  16. package/dist/esm/plugin/applyPlugin.mjs +3 -0
  17. package/dist/{plugin/esm → esm/plugin}/reduxDevtools.mjs +5 -4
  18. package/dist/esm/vanilla/selector.mjs +8 -8
  19. package/dist/esm/vanilla/state.mjs +6 -6
  20. package/package.json +9 -9
  21. package/types/middleware/applyMiddleware.d.ts +2 -2
  22. package/types/middleware/persist.d.ts +8 -8
  23. package/types/middleware/reduxDevtools.d.ts +3 -5
  24. package/types/plugin/applyPlugin.d.ts +1 -1
  25. package/types/plugin/reduxDevtools.d.ts +3 -5
  26. package/types/react/useSnapshot.d.ts +2 -2
  27. package/types/types.d.ts +28 -18
  28. package/types/vanilla/selector.d.ts +4 -6
  29. package/types/vanilla/state.d.ts +4 -6
  30. package/dist/middleware/cjs/applyMiddleware.cjs +0 -10
  31. package/dist/middleware/cjs/immer.cjs +0 -9
  32. package/dist/middleware/cjs/persist.cjs +0 -39
  33. package/dist/middleware/esm/applyMiddleware.mjs +0 -8
  34. package/dist/middleware/esm/immer.mjs +0 -7
  35. package/dist/middleware/esm/persist.mjs +0 -37
  36. package/dist/plugin/cjs/applyPlugin.cjs +0 -5
  37. package/dist/plugin/esm/applyPlugin.mjs +0 -3
  38. /package/dist/{middleware/cjs → cjs/middleware}/index.cjs +0 -0
  39. /package/dist/{plugin/cjs → cjs/plugin}/index.cjs +0 -0
  40. /package/dist/{shim/cjs → cjs/shim}/index.cjs +0 -0
  41. /package/dist/{shim/cjs → cjs/shim}/reactShim.cjs +0 -0
  42. /package/dist/{middleware/esm → esm/middleware}/index.mjs +0 -0
  43. /package/dist/{plugin/esm → esm/plugin}/index.mjs +0 -0
  44. /package/dist/{shim/esm → esm/shim}/index.mjs +0 -0
  45. /package/dist/{shim/esm → esm/shim}/reactShim.mjs +0 -0
package/README.md CHANGED
@@ -2,22 +2,23 @@
2
2
 
3
3
  > Simple, decentralized (atomic) state management for React.
4
4
 
5
- [![NPM](https://img.shields.io/npm/v/reactish-state.svg)](https://www.npmjs.com/package/reactish-state) [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state) [![bundlejs](https://img.shields.io/badge/bundlejs-.com-blue.svg)](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D)
5
+ [![NPM](https://img.shields.io/npm/v/reactish-state.svg)](https://www.npmjs.com/package/reactish-state) [![bundlejs](https://deno.bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D&badge=simple)](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D) [![NPM](https://img.shields.io/bundlephobia/minzip/reactish-state)](https://bundlephobia.com/package/reactish-state)
6
6
 
7
7
  💡 [Quick examples](#examples)    🔧 [TypeScript usage](#typescript-usage)
8
8
 
9
- ## ✨Highlights✨
9
+ ## ✨ Highlights
10
10
 
11
- - Decentralized state management
12
- - Unopinionated and easy-to-use API
13
- - No need to wrap app in Context or prop drilling
11
+ - Decentralized (atomic) state management
12
+ - Simple, unopinionated API
13
+ - No Context wrapper or prop drilling required
14
14
  - React components re-render only on changes
15
15
  - Compatible with React 18/19 concurrent rendering
16
- - Selectors are memoized by default
17
- - Feature extensible with middleware or plugins
16
+ - Built-in memoized selectors
17
+ - Extensible with middleware and plugins
18
18
  - State persistable to browser storage
19
- - Support for Redux dev tools via middleware
20
- - [Less than 1KB](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D): simple and small
19
+ - Redux DevTools support via middleware
20
+ - Fully compatible with React Compiler
21
+ - Ultra-lightweight: [<1KB](https://bundlejs.com/?q=reactish-state&treeshake=%5B*%5D&config=%7B%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%5D%7D%7D)
21
22
 
22
23
  ## Install
23
24
 
@@ -25,7 +26,38 @@
25
26
  npm install reactish-state
26
27
  ```
27
28
 
28
- ## Quick start
29
+ ## Table of Contents
30
+
31
+ - [Quick start](#quick-start)
32
+ - [State](#we-begin-by-creating-some-state)
33
+ - [State with actions](#a-state-can-also-have-custom-actions-bound-to-it)
34
+ - [Selectors](#selector-can-create-derived-state)
35
+ - [Usage in React](#use-the-state-and-selectors-in-your-react-components)
36
+ - [Why another library?](#why-another-state-management-library)
37
+ - [Why decentralized?](#why-decentralized-state-management)
38
+ - [Why not Zustand?](#why-choose-this-over-zustand)
39
+ - [Recipes](#recipes)
40
+ - [Immutable updates](#state-should-be-updated-immutably)
41
+ - [Memoized selectors](#selectors-are-memoized)
42
+ - [Async updates](#async-state-updates)
43
+ - [Accessing other state in actions](#accessing-other-state-or-selectors-inside-actions)
44
+ - [Usage outside React](#interacting-with-state-or-selectors-outside-react)
45
+ - [Destructuring actions](#destructuring-actions-for-easier-access)
46
+ - [useSelector](#selector-that-depends-on-props-or-local-state)
47
+ - [Redux-like reducers](#still-perfer-redux-like-reducers)
48
+ - [Middleware](#middleware)
49
+ - [Persist middleware](#persist-middleware)
50
+ - [Immer middleware](#immer-middleware)
51
+ - [Redux devtools middleware](#redux-devtools-middleware)
52
+ - [Multiple middleware](#using-multiple-middleware)
53
+ - [Different middleware](#using-different-middleware-in-different-states)
54
+ - [Plugins](#plugins)
55
+ - [Redux devtools plugin](#redux-devtools-plugin)
56
+ - [TypeScript usage](#typescript-usage)
57
+ - [Examples](#examples)
58
+ - [React 16/17 setup](#react-1617-setup)
59
+
60
+ # Quick start
29
61
 
30
62
  ### We begin by creating some state
31
63
 
@@ -384,21 +416,17 @@ dispatch({ type: "DECREASE", by: 7 });
384
416
  console.log(countState.get()); // Print 3
385
417
  ```
386
418
 
387
- ## Middleware
419
+ # Middleware
388
420
 
389
- You can enhance the functionality of state with middleware. Instead of using the `state` export, use the `createState` export from the library. Middleware is a function that receives `set`, `get`, and `subscribe`, and should return a new set function.
421
+ You can enhance state functionality using middleware. Instead of the `state` export, use the `stateBuilder` export from the library. A middleware is a function that receives a state object and returns a new `set` function, which can add custom behavior while still calling the current `set` in the middleware chain.
390
422
 
391
423
  ```js
392
- import { createState } from "reactish-state";
393
-
394
- const state = createState({
395
- middleware:
396
- ({ set, get }) =>
397
- (...args) => {
398
- set(...args);
399
- // Log the state every time after calling `set`
400
- console.log("New state", get());
401
- }
424
+ import { stateBuilder } from "reactish-state";
425
+
426
+ const state = stateBuilder(({ set, get }) => (...args) => {
427
+ set(...args);
428
+ // Log the state every time after calling `set`
429
+ console.log("New state", get());
402
430
  });
403
431
 
404
432
  // Now the `state` function has middleware wired up
@@ -420,13 +448,13 @@ filterState.set("COMPLETED"); // Print "New state 'COMPLETED'"
420
448
  You can save the state to browser storage using the `persist` middleware.
421
449
 
422
450
  ```js
423
- import { createState } from "reactish-state";
451
+ import { stateBuilder } from "reactish-state";
424
452
  import { persist } from "reactish-state/middleware";
425
453
 
426
454
  // Create the persist middleware,
427
455
  // optionally provide a `prefix` to prepend to the keys in storage
428
456
  const persistMiddleware = persist({ prefix: "myApp-" });
429
- const state = createState({ middleware: persistMiddleware });
457
+ const state = stateBuilder(persistMiddleware.middleware);
430
458
 
431
459
  const countState = state(
432
460
  0,
@@ -457,10 +485,10 @@ const persistMiddleware = persist({ getStorage: () => sessionStorage });
457
485
  You can update state mutably using the `immer` middleware.
458
486
 
459
487
  ```js
460
- import { createState } from "reactish-state";
488
+ import { stateBuilder } from "reactish-state";
461
489
  import { immer } from "reactish-state/middleware/immer";
462
490
 
463
- const state = createState({ middleware: immer });
491
+ const state = stateBuilder(immer);
464
492
 
465
493
  let todoId = 1;
466
494
  const todos = state([], (set) => ({
@@ -488,10 +516,10 @@ todos.toggle(1);
488
516
  This middleware provides integration with the Redux DevTools browser extension. Individual states are combined into a single object in Redux DevTools for easy inspection.
489
517
 
490
518
  ```js
491
- import { createState } from "reactish-state";
519
+ import { stateBuilder } from "reactish-state";
492
520
  import { reduxDevtools } from "reactish-state/middleware";
493
521
 
494
- const state = createState({ middleware: reduxDevtools({ name: "todoApp" }) });
522
+ const state = stateBuilder(reduxDevtools({ name: "todoApp" }));
495
523
 
496
524
  const todos = state(
497
525
  [],
@@ -523,14 +551,14 @@ const filter = state("ALL", null, { key: "filter" });
523
551
 
524
552
  ## Using multiple middleware
525
553
 
526
- Middleware is chainable. You can use the `applyMiddleware` utility to chain multiple middleware and pass the result to `createState`.
554
+ Middleware is chainable. You can use the `applyMiddleware` utility to chain multiple middleware and pass the result to `stateBuilder`.
527
555
 
528
556
  ```js
529
557
  import { applyMiddleware } from "reactish-state/middleware";
530
558
 
531
- const state = createState({
532
- middleware: applyMiddleware([immer, reduxDevtools(), persist()])
533
- });
559
+ const state = stateBuilder(
560
+ applyMiddleware([reduxDevtools(), persist().middleware, immer])
561
+ );
534
562
  ```
535
563
 
536
564
  ## Using different middleware in different states
@@ -538,44 +566,38 @@ const state = createState({
538
566
  This is naturally achievable thanks to the decentralized state model.
539
567
 
540
568
  ```js
541
- const persistState = createState({ middleware: persist() });
542
- const immerState = createState({ middleware: immer });
569
+ const persistableState = stateBuilder(persist().middleware);
570
+ const immerState = stateBuilder(immer);
543
571
 
544
- const visibilityFilter = persistState("ALL"); // Will be persisted
572
+ const visibilityFilter = persistableState("ALL"); // Will be persisted
545
573
  const todos = immerState([]); // Can be mutated
546
574
  ```
547
575
 
548
576
  This also eliminates the need to implement a whitelist or blacklist in the persist middleware.
549
577
 
550
- ## Plugins
578
+ # Plugins
551
579
 
552
- While middleware enhances state, plugins allow you to hook into selectors. The key difference is that plugins don’t return a `set` function, as selectors are read-only. Similarly, you use the `createSelector` export from the library instead of `selector`.
580
+ While middleware enhances state, plugins allow you to hook into selectors. The key difference is that plugins don’t return a `set` function, as selectors are read-only. Similarly, you use the `selectorBuilder` export from the library instead of `selector`.
553
581
 
554
582
  ```js
555
- import { state, createSelector } from "reactish-state";
556
-
557
- const selector = createSelector({
558
- plugin: ({ get, subscribe }, config) => {
559
- subscribe(() => {
560
- // Log the selector value every time it changes
561
- // `config` can hold contextual data from the selector
562
- console.log(`${config?.key} selector:`, get());
563
- });
564
- }
583
+ import { state, selectorBuilder } from "reactish-state";
584
+
585
+ const selector = selectorBuilder(({ get, subscribe, meta }) => {
586
+ subscribe(() => {
587
+ // Log the selector value every time it changes
588
+ // `meta` returns metadata of the selector
589
+ console.log(`${meta()} selector:`, get());
590
+ });
565
591
  });
566
592
 
567
593
  const countState = state(0);
568
594
  const doubleSelector = selector(
569
595
  countState,
570
596
  (count) => count * 2,
571
- // Provide contextual data in the last parameter to identify the selector
572
- {
573
- key: "double"
574
- }
597
+ // Provide metadata in the last parameter to identify the selector
598
+ "double"
575
599
  );
576
- const squareSelector = selector(countState, (count) => count * count, {
577
- key: "square"
578
- });
600
+ const squareSelector = selector(countState, (count) => count * count, "square");
579
601
 
580
602
  countState.set(5); // Logs - double selector: 10, square selector: 25
581
603
  ```
@@ -587,10 +609,10 @@ Likewise, there is an `applyPlugin` function for applying multiple plugins.
587
609
  Individual selectors are combined into a single object in Redux DevTools for easy inspection.
588
610
 
589
611
  ```js
590
- import { createSelector } from "reactish-state";
612
+ import { selectorBuilder } from "reactish-state";
591
613
  import { reduxDevtools } from "reactish-state/plugin";
592
614
 
593
- const selector = createSelector({ plugin: reduxDevtools() });
615
+ const selector = selectorBuilder(reduxDevtools());
594
616
  // Then use the `selector` as usual...
595
617
  ```
596
618
 
@@ -8,10 +8,10 @@ var shim = require('./react/shim.cjs');
8
8
 
9
9
 
10
10
 
11
- exports.createState = state.createState;
12
11
  exports.state = state.state;
13
- exports.createSelector = selector.createSelector;
12
+ exports.stateBuilder = state.stateBuilder;
14
13
  exports.selector = selector.selector;
14
+ exports.selectorBuilder = selector.selectorBuilder;
15
15
  exports.useSnapshot = useSnapshot.useSnapshot;
16
16
  exports.useSelector = useSelector.useSelector;
17
17
  exports.setReactShim = shim.setReactShim;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const applyMiddleware = (middlewares, {
4
+ fromRight
5
+ } = {}) => api => middlewares[fromRight ? 'reduceRight' : 'reduce']((set, middleware) => middleware ? middleware({
6
+ ...api,
7
+ set
8
+ }) : set, api.set);
9
+
10
+ exports.applyMiddleware = applyMiddleware;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ var immer$1 = require('immer');
4
+
5
+ const immer = ({
6
+ set
7
+ }) => (value, context) => set(typeof value === 'function' ? immer$1.produce(value) : value, context);
8
+
9
+ exports.immer = immer;
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const persist = ({
4
+ prefix,
5
+ getStorage = () => localStorage
6
+ } = {}) => {
7
+ const states = [];
8
+ return {
9
+ middleware: ({
10
+ set,
11
+ get,
12
+ meta
13
+ }) => {
14
+ let key = meta()?.key;
15
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
16
+ if (prefix) key = prefix + key;
17
+ states.push([key, set]);
18
+ return (...args) => {
19
+ set(...args);
20
+ try {
21
+ getStorage().setItem(key, JSON.stringify(get()));
22
+ } catch (_unused) {
23
+ /* continue regardless of error */
24
+ }
25
+ };
26
+ },
27
+ hydrate: () => {
28
+ states.forEach(([key, set]) => {
29
+ try {
30
+ const value = getStorage().getItem(key);
31
+ value != null && set(value !== 'undefined' ? JSON.parse(value) : undefined, `HYDRATE_${key}`);
32
+ } catch (_unused2) {
33
+ /* continue regardless of error */
34
+ }
35
+ });
36
+ states.length = 0;
37
+ }
38
+ };
39
+ };
40
+
41
+ exports.persist = persist;
@@ -4,22 +4,24 @@ const reduxDevtools = ({
4
4
  name
5
5
  } = {}) => {
6
6
  let devtoolsExt;
7
- if (process.env.NODE_ENV === 'production' || typeof window === 'undefined' || !(devtoolsExt = window.__REDUX_DEVTOOLS_EXTENSION__)) return;
7
+ if (process.env.NODE_ENV === 'production' || typeof window === 'undefined' || !(devtoolsExt = window.__REDUX_DEVTOOLS_EXTENSION__)) {
8
+ return;
9
+ }
8
10
  const devtools = devtoolsExt.connect({
9
11
  name
10
12
  });
11
13
  const mergedState = {};
12
14
  return ({
13
15
  set,
14
- get
15
- }, config) => {
16
- const key = config?.key;
17
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` middleware is used.');
16
+ get,
17
+ meta
18
+ }) => {
19
+ const key = meta()?.key;
20
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` middleware is used.');
18
21
  mergedState[key] = get();
19
22
  devtools.init(mergedState);
20
- return (...args) => {
21
- const [value, action] = args;
22
- set(...args);
23
+ return (value, action) => {
24
+ set(value, action);
23
25
  mergedState[key] = get();
24
26
  devtools.send(typeof action === 'string' ? {
25
27
  type: action
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const applyPlugin = plugins => selector => plugins.forEach(plugin => plugin?.(selector));
4
+
5
+ exports.applyPlugin = applyPlugin;
@@ -11,10 +11,11 @@ const reduxDevtools = ({
11
11
  const mergedState = {};
12
12
  return ({
13
13
  get,
14
- subscribe
15
- }, config) => {
16
- const key = config?.key;
17
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` plugin is used.');
14
+ subscribe,
15
+ meta
16
+ }) => {
17
+ const key = meta()?.key;
18
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] selector should be provided with a string `key` in the config object when the `reduxDevtools` plugin is used.');
18
19
  const updateState = () => {
19
20
  mergedState[key] = get();
20
21
  devtools.init(mergedState);
@@ -2,13 +2,11 @@
2
2
 
3
3
  var utils = require('../utils.cjs');
4
4
 
5
- const createSelector = ({
6
- plugin
7
- } = {}) => (...items) => {
5
+ const selectorBuilder = plugin => (...items) => {
8
6
  const length = items.length;
9
7
  const cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
10
8
  const selectorFunc = items[cutoff];
11
- const config = items[cutoff + 1];
9
+ const metadata = items[cutoff + 1];
12
10
  items.length = cutoff;
13
11
  let cache;
14
12
  const selector = {
@@ -19,12 +17,14 @@ const createSelector = ({
19
17
  cache = [args, value];
20
18
  return value;
21
19
  },
22
- subscribe: utils.createSubscriber(items)
20
+ subscribe: utils.createSubscriber(items),
21
+ meta: () => metadata
23
22
  };
24
- plugin?.(selector, config);
23
+ plugin?.(selector);
25
24
  return selector;
25
+ // Wrap TSelectorMeta in a tuple to prevent conditional type distribution;
26
26
  };
27
- const selector = /*#__PURE__*/createSelector();
27
+ const selector = /*#__PURE__*/selectorBuilder();
28
28
 
29
- exports.createSelector = createSelector;
30
29
  exports.selector = selector;
30
+ exports.selectorBuilder = selectorBuilder;
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const createState = ({
4
- middleware
5
- } = {}) => (initialValue, actionBuilder, config) => {
3
+ const stateBuilder = middleware => (initialValue, actionBuilder, metadata) => {
6
4
  let value = initialValue;
7
5
  const listeners = new Set();
8
6
  const get = () => value;
9
7
  const readonlyState = {
10
8
  get,
9
+ meta: () => metadata,
11
10
  subscribe: listener => {
12
11
  listeners.add(listener);
13
12
  return () => listeners.delete(listener);
@@ -24,14 +23,15 @@ const createState = ({
24
23
  if (middleware) set = middleware({
25
24
  ...readonlyState,
26
25
  set
27
- }, config);
26
+ });
28
27
  return {
29
28
  ...actionBuilder?.(set, get),
30
29
  ...readonlyState,
31
30
  set
32
31
  };
32
+ // Wrap TStateMeta in a tuple to prevent conditional type distribution
33
33
  };
34
- const state = /*#__PURE__*/createState();
34
+ const state = /*#__PURE__*/stateBuilder();
35
35
 
36
- exports.createState = createState;
37
36
  exports.state = state;
37
+ exports.stateBuilder = stateBuilder;
@@ -1,5 +1,5 @@
1
- export { createState, state } from './vanilla/state.mjs';
2
- export { createSelector, selector } from './vanilla/selector.mjs';
1
+ export { state, stateBuilder } from './vanilla/state.mjs';
2
+ export { selector, selectorBuilder } from './vanilla/selector.mjs';
3
3
  export { useSnapshot } from './react/useSnapshot.mjs';
4
4
  export { useSelector } from './react/useSelector.mjs';
5
5
  export { setReactShim } from './react/shim.mjs';
@@ -0,0 +1,8 @@
1
+ const applyMiddleware = (middlewares, {
2
+ fromRight
3
+ } = {}) => api => middlewares[fromRight ? 'reduceRight' : 'reduce']((set, middleware) => middleware ? middleware({
4
+ ...api,
5
+ set
6
+ }) : set, api.set);
7
+
8
+ export { applyMiddleware };
@@ -0,0 +1,7 @@
1
+ import { produce } from 'immer';
2
+
3
+ const immer = ({
4
+ set
5
+ }) => (value, context) => set(typeof value === 'function' ? produce(value) : value, context);
6
+
7
+ export { immer };
@@ -0,0 +1,39 @@
1
+ const persist = ({
2
+ prefix,
3
+ getStorage = () => localStorage
4
+ } = {}) => {
5
+ const states = [];
6
+ return {
7
+ middleware: ({
8
+ set,
9
+ get,
10
+ meta
11
+ }) => {
12
+ let key = meta()?.key;
13
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
14
+ if (prefix) key = prefix + key;
15
+ states.push([key, set]);
16
+ return (...args) => {
17
+ set(...args);
18
+ try {
19
+ getStorage().setItem(key, JSON.stringify(get()));
20
+ } catch (_unused) {
21
+ /* continue regardless of error */
22
+ }
23
+ };
24
+ },
25
+ hydrate: () => {
26
+ states.forEach(([key, set]) => {
27
+ try {
28
+ const value = getStorage().getItem(key);
29
+ value != null && set(value !== 'undefined' ? JSON.parse(value) : undefined, `HYDRATE_${key}`);
30
+ } catch (_unused2) {
31
+ /* continue regardless of error */
32
+ }
33
+ });
34
+ states.length = 0;
35
+ }
36
+ };
37
+ };
38
+
39
+ export { persist };
@@ -2,22 +2,24 @@ const reduxDevtools = ({
2
2
  name
3
3
  } = {}) => {
4
4
  let devtoolsExt;
5
- if (process.env.NODE_ENV === 'production' || typeof window === 'undefined' || !(devtoolsExt = window.__REDUX_DEVTOOLS_EXTENSION__)) return;
5
+ if (process.env.NODE_ENV === 'production' || typeof window === 'undefined' || !(devtoolsExt = window.__REDUX_DEVTOOLS_EXTENSION__)) {
6
+ return;
7
+ }
6
8
  const devtools = devtoolsExt.connect({
7
9
  name
8
10
  });
9
11
  const mergedState = {};
10
12
  return ({
11
13
  set,
12
- get
13
- }, config) => {
14
- const key = config?.key;
15
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` middleware is used.');
14
+ get,
15
+ meta
16
+ }) => {
17
+ const key = meta()?.key;
18
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` middleware is used.');
16
19
  mergedState[key] = get();
17
20
  devtools.init(mergedState);
18
- return (...args) => {
19
- const [value, action] = args;
20
- set(...args);
21
+ return (value, action) => {
22
+ set(value, action);
21
23
  mergedState[key] = get();
22
24
  devtools.send(typeof action === 'string' ? {
23
25
  type: action
@@ -0,0 +1,3 @@
1
+ const applyPlugin = plugins => selector => plugins.forEach(plugin => plugin?.(selector));
2
+
3
+ export { applyPlugin };
@@ -9,10 +9,11 @@ const reduxDevtools = ({
9
9
  const mergedState = {};
10
10
  return ({
11
11
  get,
12
- subscribe
13
- }, config) => {
14
- const key = config?.key;
15
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `reduxDevtools` plugin is used.');
12
+ subscribe,
13
+ meta
14
+ }) => {
15
+ const key = meta()?.key;
16
+ if (process.env.NODE_ENV !== 'production' && !key) throw new Error('[reactish-state] selector should be provided with a string `key` in the config object when the `reduxDevtools` plugin is used.');
16
17
  const updateState = () => {
17
18
  mergedState[key] = get();
18
19
  devtools.init(mergedState);
@@ -1,12 +1,10 @@
1
1
  import { createSubscriber, getSelectorValues, isEqual } from '../utils.mjs';
2
2
 
3
- const createSelector = ({
4
- plugin
5
- } = {}) => (...items) => {
3
+ const selectorBuilder = plugin => (...items) => {
6
4
  const length = items.length;
7
5
  const cutoff = typeof items[length - 1] === 'function' ? length - 1 : length - 2;
8
6
  const selectorFunc = items[cutoff];
9
- const config = items[cutoff + 1];
7
+ const metadata = items[cutoff + 1];
10
8
  items.length = cutoff;
11
9
  let cache;
12
10
  const selector = {
@@ -17,11 +15,13 @@ const createSelector = ({
17
15
  cache = [args, value];
18
16
  return value;
19
17
  },
20
- subscribe: createSubscriber(items)
18
+ subscribe: createSubscriber(items),
19
+ meta: () => metadata
21
20
  };
22
- plugin?.(selector, config);
21
+ plugin?.(selector);
23
22
  return selector;
23
+ // Wrap TSelectorMeta in a tuple to prevent conditional type distribution;
24
24
  };
25
- const selector = /*#__PURE__*/createSelector();
25
+ const selector = /*#__PURE__*/selectorBuilder();
26
26
 
27
- export { createSelector, selector };
27
+ export { selector, selectorBuilder };
@@ -1,11 +1,10 @@
1
- const createState = ({
2
- middleware
3
- } = {}) => (initialValue, actionBuilder, config) => {
1
+ const stateBuilder = middleware => (initialValue, actionBuilder, metadata) => {
4
2
  let value = initialValue;
5
3
  const listeners = new Set();
6
4
  const get = () => value;
7
5
  const readonlyState = {
8
6
  get,
7
+ meta: () => metadata,
9
8
  subscribe: listener => {
10
9
  listeners.add(listener);
11
10
  return () => listeners.delete(listener);
@@ -22,13 +21,14 @@ const createState = ({
22
21
  if (middleware) set = middleware({
23
22
  ...readonlyState,
24
23
  set
25
- }, config);
24
+ });
26
25
  return {
27
26
  ...actionBuilder?.(set, get),
28
27
  ...readonlyState,
29
28
  set
30
29
  };
30
+ // Wrap TStateMeta in a tuple to prevent conditional type distribution
31
31
  };
32
- const state = /*#__PURE__*/createState();
32
+ const state = /*#__PURE__*/stateBuilder();
33
33
 
34
- export { createState, state };
34
+ export { state, stateBuilder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactish-state",
3
- "version": "1.2.1-alpha.2",
3
+ "version": "2.0.0-alpha.0",
4
4
  "description": "Simple, decentralized (atomic) state management for React.",
5
5
  "author": "Zheng Song",
6
6
  "license": "MIT",
@@ -50,23 +50,23 @@
50
50
  },
51
51
  "./middleware": {
52
52
  "types": "./types/middleware/index.d.ts",
53
- "require": "./dist/middleware/cjs/index.cjs",
54
- "default": "./dist/middleware/esm/index.mjs"
53
+ "require": "./dist/cjs/middleware/index.cjs",
54
+ "default": "./dist/esm/middleware/index.mjs"
55
55
  },
56
56
  "./middleware/immer": {
57
57
  "types": "./types/middleware/immer.d.ts",
58
- "require": "./dist/middleware/cjs/immer.cjs",
59
- "default": "./dist/middleware/esm/immer.mjs"
58
+ "require": "./dist/cjs/middleware/immer.cjs",
59
+ "default": "./dist/esm/middleware/immer.mjs"
60
60
  },
61
61
  "./plugin": {
62
62
  "types": "./types/plugin/index.d.ts",
63
- "require": "./dist/plugin/cjs/index.cjs",
64
- "default": "./dist/plugin/esm/index.mjs"
63
+ "require": "./dist/cjs/plugin/index.cjs",
64
+ "default": "./dist/esm/plugin/index.mjs"
65
65
  },
66
66
  "./shim": {
67
67
  "types": "./types/shim/index.d.ts",
68
- "require": "./dist/shim/cjs/index.cjs",
69
- "default": "./dist/shim/esm/index.mjs"
68
+ "require": "./dist/cjs/shim/index.cjs",
69
+ "default": "./dist/esm/shim/index.mjs"
70
70
  },
71
71
  "./package.json": "./package.json"
72
72
  },
@@ -1,5 +1,5 @@
1
1
  import type { Middleware } from '../types';
2
- declare const applyMiddleware: <TConfig>(middlewares: (Middleware<TConfig> | undefined)[], options?: {
2
+ declare const applyMiddleware: <TStateMeta>(middlewares: (Middleware<TStateMeta> | undefined)[], options?: {
3
3
  fromRight?: boolean;
4
- }) => Middleware<TConfig>;
4
+ }) => Middleware<TStateMeta>;
5
5
  export { applyMiddleware };
@@ -1,11 +1,11 @@
1
- import type { Middleware, Config } from '../types';
2
- interface PersistMiddleware extends Middleware<Config> {
3
- hydrate(this: void): void;
1
+ import type { Middleware, Metadata } from '../types';
2
+ interface Persist<TStateMeta extends Metadata> {
3
+ middleware: Middleware<TStateMeta>;
4
+ hydrate: () => void;
4
5
  }
5
- type Persist = (options?: {
6
+ declare const persist: <TStateMeta extends Metadata>({ prefix, getStorage }?: {
6
7
  prefix?: string;
7
- getStorage?: () => Pick<Storage, 'getItem' | 'setItem'>;
8
- }) => PersistMiddleware;
9
- declare const persist: Persist;
10
- export type { Persist, PersistMiddleware };
8
+ getStorage?: () => Pick<Storage, "getItem" | "setItem">;
9
+ }) => Persist<TStateMeta>;
10
+ export type { Persist };
11
11
  export { persist };
@@ -1,7 +1,5 @@
1
- import type { Middleware, Config } from '../types';
2
- type ReduxDevtools = (options?: {
1
+ import type { Middleware, Metadata } from '../types';
2
+ declare const reduxDevtools: <TStateMeta extends Metadata>({ name }?: {
3
3
  name?: string;
4
- }) => Middleware<Config> | undefined;
5
- declare const reduxDevtools: ReduxDevtools;
6
- export type { ReduxDevtools };
4
+ }) => Middleware<TStateMeta> | undefined;
7
5
  export { reduxDevtools };
@@ -1,3 +1,3 @@
1
1
  import type { Plugin } from '../types';
2
- declare const applyPlugin: <TConfig>(plugins: (Plugin<TConfig> | undefined)[]) => Plugin<TConfig>;
2
+ declare const applyPlugin: <TSelectorMeta>(plugins: (Plugin<TSelectorMeta> | undefined)[]) => Plugin<TSelectorMeta>;
3
3
  export { applyPlugin };
@@ -1,7 +1,5 @@
1
- import type { Plugin, Config } from '../types';
2
- type ReduxDevtools = (options?: {
1
+ import type { Plugin, Metadata } from '../types';
2
+ declare const reduxDevtools: <TSelectorMeta extends Metadata>({ name }?: {
3
3
  name?: string;
4
- }) => Plugin<Config> | undefined;
5
- declare const reduxDevtools: ReduxDevtools;
6
- export type { ReduxDevtools };
4
+ }) => Plugin<TSelectorMeta> | undefined;
7
5
  export { reduxDevtools };
@@ -1,3 +1,3 @@
1
- import type { Selector } from '../types';
2
- declare const useSnapshot: <TValue>({ subscribe, get }: Selector<TValue>) => TValue;
1
+ import type { Observable } from '../types';
2
+ declare const useSnapshot: <TValue>({ subscribe, get }: Observable<TValue>) => TValue;
3
3
  export { useSnapshot };
package/types/types.d.ts CHANGED
@@ -1,35 +1,42 @@
1
+ export interface Metadata {
2
+ key: string;
3
+ }
1
4
  export type Getter<TValue> = () => TValue;
2
5
  export type Setter<TValue, TContext = unknown> = (newValue: TValue | ((value: TValue) => TValue), context?: TContext) => void;
3
6
  export type Unsubscriber = () => void;
4
7
  export type StateListener<TValue> = (nextValue: TValue, prevValue: TValue) => void;
5
8
  export type StateSubscriber<TValue> = (listener: StateListener<TValue>) => Unsubscriber;
6
- export interface State<TValue, TContext = unknown> {
9
+ export interface State<TValue, TMeta = unknown, TContext = unknown> {
7
10
  get: Getter<TValue>;
8
11
  set: Setter<TValue, TContext>;
9
12
  subscribe: StateSubscriber<TValue>;
13
+ meta: () => TMeta;
14
+ }
15
+ export type StateWithAction<TValue, TAction, TMeta = unknown, TContext = unknown> = Omit<TAction, keyof State<TValue, TMeta, TContext>> & State<TValue, TMeta, TContext>;
16
+ export type ActionBuilder<TValue, TAction, TContext = unknown> = (set: Setter<TValue, TContext>, get: Getter<TValue>) => TAction;
17
+ export interface StateBuilder {
18
+ <TValue, TContext = unknown>(): State<TValue | undefined, undefined, TContext>;
19
+ <TValue, TContext = unknown>(initialValue: TValue): State<TValue, undefined, TContext>;
20
+ <TValue, TMeta, TContext = unknown>(initialValue: TValue, actionBuilder: null | undefined, metadata: TMeta): State<TValue, TMeta, TContext>;
21
+ <TValue, TAction, TContext = unknown>(initialValue: TValue, actionBuilder: ActionBuilder<TValue, TAction, TContext>): StateWithAction<TValue, TAction, undefined, TContext>;
22
+ <TValue, TAction, TMeta, TContext = unknown>(initialValue: TValue, actionBuilder: ActionBuilder<TValue, TAction, TContext>, metadata: TMeta): StateWithAction<TValue, TAction, TMeta, TContext>;
10
23
  }
11
- export type StateWithAction<TValue, TAction, TContext = unknown> = Omit<TAction, keyof State<TValue, TContext>> & State<TValue, TContext>;
12
- export type ActionBuilder<TValue, TAction, TContext = unknown> = (set: Setter<TValue, TContext>, get: () => TValue) => TAction;
13
- export interface StateBuilder<TConfig = unknown> {
14
- <TValue, TContext = unknown>(): State<TValue | undefined, TContext>;
15
- <TValue, TContext = unknown>(initialValue: TValue): State<TValue, TContext>;
16
- <TValue, TContext = unknown>(initialValue: TValue, actionBuilder: null | undefined, config?: TConfig): State<TValue, TContext>;
17
- <TValue, TAction, TContext = unknown>(initialValue: TValue, actionBuilder: ActionBuilder<TValue, TAction, TContext>, config?: TConfig): StateWithAction<TValue, TAction, TContext>;
24
+ export interface StateBuilderWithMeta<TStateMeta> {
25
+ <TValue, TMeta extends TStateMeta, TContext = unknown>(initialValue: TValue, actionBuilder: null | undefined, metadata: TMeta): State<TValue, TMeta, TContext>;
26
+ <TValue, TAction, TMeta extends TStateMeta, TContext = unknown>(initialValue: TValue, actionBuilder: ActionBuilder<TValue, TAction, TContext>, metadata: TMeta): StateWithAction<TValue, TAction, TMeta, TContext>;
18
27
  }
19
28
  export type SelectorListener = () => void;
20
29
  export type SelectorSubscriber = (listener: SelectorListener) => Unsubscriber;
21
- export interface Selector<TValue> {
30
+ export interface Observable<TValue> {
22
31
  get: Getter<TValue>;
23
32
  subscribe: SelectorSubscriber;
24
33
  }
25
- export interface Config {
26
- key?: string;
34
+ export interface Selector<TValue, TMeta = unknown> extends Observable<TValue> {
35
+ meta: () => TMeta;
27
36
  }
28
- export interface Middleware<TConfig = unknown> {
29
- <TValue, TContext = unknown>(state: State<TValue, TContext>, config?: TConfig): Setter<TValue, TContext>;
30
- }
31
- export interface Plugin<TConfig = unknown> {
32
- <TValue>(selector: Selector<TValue>, config?: TConfig): void;
37
+ export type Middleware<TStateMeta = never> = <TValue, TMeta extends TStateMeta, TContext = unknown>(state: State<TValue, TMeta, TContext>) => Setter<TValue, TContext>;
38
+ export interface Plugin<TSelectorMeta = never> {
39
+ <TValue, TMeta extends TSelectorMeta>(selector: Selector<TValue, TMeta>): void;
33
40
  }
34
41
  export type SelectorArray = Selector<unknown>[];
35
42
  export type SelectorValueArray<TArray extends SelectorArray> = {
@@ -40,7 +47,10 @@ export type SelectorParams<TArray extends SelectorArray, TValue> = [
40
47
  ...TArray,
41
48
  SelectorFunc<TArray, TValue>
42
49
  ];
43
- export interface SelectorBuilder<TConfig = unknown> {
50
+ export interface SelectorBuilder {
44
51
  <TArray extends SelectorArray, TValue>(...items: SelectorParams<TArray, TValue>): Selector<TValue>;
45
- <TArray extends SelectorArray, TValue>(...items: [...SelectorParams<TArray, TValue>, TConfig]): Selector<TValue>;
52
+ <TArray extends SelectorArray, TValue, TMeta>(...items: [...SelectorParams<TArray, TValue>, TMeta]): Selector<TValue>;
53
+ }
54
+ export interface SelectorBuilderWithMeta<TSelectorMeta> {
55
+ <TArray extends SelectorArray, TValue, TMeta extends TSelectorMeta>(...items: [...SelectorParams<TArray, TValue>, TMeta]): Selector<TValue>;
46
56
  }
@@ -1,6 +1,4 @@
1
- import type { Plugin, SelectorBuilder } from '../types';
2
- declare const createSelector: <TConfig>({ plugin }?: {
3
- plugin?: Plugin<TConfig>;
4
- }) => SelectorBuilder<TConfig>;
5
- declare const selector: SelectorBuilder<unknown>;
6
- export { selector, createSelector };
1
+ import type { Plugin, SelectorBuilder, SelectorBuilderWithMeta } from '../types';
2
+ declare const selectorBuilder: <TSelectorMeta = never>(plugin?: Plugin<TSelectorMeta>) => [TSelectorMeta] extends [never] ? SelectorBuilder : SelectorBuilderWithMeta<TSelectorMeta>;
3
+ declare const selector: SelectorBuilder;
4
+ export { selector, selectorBuilder };
@@ -1,6 +1,4 @@
1
- import type { StateBuilder, Middleware } from '../types';
2
- declare const createState: <TConfig>({ middleware }?: {
3
- middleware?: Middleware<TConfig>;
4
- }) => StateBuilder<TConfig>;
5
- declare const state: StateBuilder<unknown>;
6
- export { state, createState };
1
+ import type { StateBuilder, StateBuilderWithMeta, Middleware } from '../types';
2
+ declare const stateBuilder: <TStateMeta = never>(middleware?: Middleware<TStateMeta>) => [TStateMeta] extends [never] ? StateBuilder : StateBuilderWithMeta<TStateMeta>;
3
+ declare const state: StateBuilder;
4
+ export { state, stateBuilder };
@@ -1,10 +0,0 @@
1
- 'use strict';
2
-
3
- const applyMiddleware = (middlewares, {
4
- fromRight
5
- } = {}) => (api, config) => middlewares[fromRight ? 'reduceRight' : 'reduce']((set, middleware) => middleware ? middleware({
6
- ...api,
7
- set
8
- }, config) : set, api.set);
9
-
10
- exports.applyMiddleware = applyMiddleware;
@@ -1,9 +0,0 @@
1
- 'use strict';
2
-
3
- var immer$1 = require('immer');
4
-
5
- const immer = ({
6
- set
7
- }) => (value, ...rest) => set(typeof value === 'function' ? immer$1.produce(value) : value, ...rest);
8
-
9
- exports.immer = immer;
@@ -1,39 +0,0 @@
1
- 'use strict';
2
-
3
- const persist = ({
4
- prefix,
5
- getStorage = () => localStorage
6
- } = {}) => {
7
- const states = [];
8
- const middleware = ({
9
- set,
10
- get
11
- }, config) => {
12
- let key = config?.key || '';
13
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
14
- if (prefix) key = prefix + key;
15
- states.push([key, set]);
16
- return (...args) => {
17
- set(...args);
18
- try {
19
- getStorage().setItem(key, JSON.stringify(get()));
20
- } catch (_unused) {
21
- /* continue regardless of error */
22
- }
23
- };
24
- };
25
- middleware.hydrate = () => {
26
- states.forEach(([key, set]) => {
27
- try {
28
- const value = getStorage().getItem(key);
29
- value != null && set(value !== 'undefined' ? JSON.parse(value) : undefined, `HYDRATE_${key}`);
30
- } catch (_unused2) {
31
- /* continue regardless of error */
32
- }
33
- });
34
- states.length = 0;
35
- };
36
- return middleware;
37
- };
38
-
39
- exports.persist = persist;
@@ -1,8 +0,0 @@
1
- const applyMiddleware = (middlewares, {
2
- fromRight
3
- } = {}) => (api, config) => middlewares[fromRight ? 'reduceRight' : 'reduce']((set, middleware) => middleware ? middleware({
4
- ...api,
5
- set
6
- }, config) : set, api.set);
7
-
8
- export { applyMiddleware };
@@ -1,7 +0,0 @@
1
- import { produce } from 'immer';
2
-
3
- const immer = ({
4
- set
5
- }) => (value, ...rest) => set(typeof value === 'function' ? produce(value) : value, ...rest);
6
-
7
- export { immer };
@@ -1,37 +0,0 @@
1
- const persist = ({
2
- prefix,
3
- getStorage = () => localStorage
4
- } = {}) => {
5
- const states = [];
6
- const middleware = ({
7
- set,
8
- get
9
- }, config) => {
10
- let key = config?.key || '';
11
- if (!key) throw new Error('[reactish-state] state should be provided with a string `key` in the config object when the `persist` middleware is used.');
12
- if (prefix) key = prefix + key;
13
- states.push([key, set]);
14
- return (...args) => {
15
- set(...args);
16
- try {
17
- getStorage().setItem(key, JSON.stringify(get()));
18
- } catch (_unused) {
19
- /* continue regardless of error */
20
- }
21
- };
22
- };
23
- middleware.hydrate = () => {
24
- states.forEach(([key, set]) => {
25
- try {
26
- const value = getStorage().getItem(key);
27
- value != null && set(value !== 'undefined' ? JSON.parse(value) : undefined, `HYDRATE_${key}`);
28
- } catch (_unused2) {
29
- /* continue regardless of error */
30
- }
31
- });
32
- states.length = 0;
33
- };
34
- return middleware;
35
- };
36
-
37
- export { persist };
@@ -1,5 +0,0 @@
1
- 'use strict';
2
-
3
- const applyPlugin = plugins => (selector, config) => plugins.forEach(plugin => plugin?.(selector, config));
4
-
5
- exports.applyPlugin = applyPlugin;
@@ -1,3 +0,0 @@
1
- const applyPlugin = plugins => (selector, config) => plugins.forEach(plugin => plugin?.(selector, config));
2
-
3
- export { applyPlugin };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes