juststore 0.0.6 → 0.1.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.
package/README.md CHANGED
@@ -291,7 +291,7 @@ function TemperatureInput() {
291
291
 
292
292
  ```tsx
293
293
  function TotalPrice() {
294
- const total = store.cart.items.compute(
294
+ const total = store.cart.items.useCompute(
295
295
  items => items?.reduce((sum, item) => sum + item.price * item.qty, 0) ?? 0
296
296
  )
297
297
  return <span>Total: ${total}</span>
@@ -450,23 +450,42 @@ Creates a component-scoped store that doesn't persist.
450
450
 
451
451
  Creates a form store with validation support.
452
452
 
453
+ ### Root Node Methods
454
+
455
+ The store root provides path-based methods for dynamic access:
456
+
457
+ | Method | Description |
458
+ | ------------------------------- | ------------------------------------------------------- |
459
+ | `.use(path)` | Subscribe and read value (triggers re-render on change) |
460
+ | `.useDebounce(path, ms)` | Subscribe with debounced updates |
461
+ | `.useState(path)` | Returns `[value, setValue]` tuple |
462
+ | `.value(path)` | Read without subscribing |
463
+ | `.set(path, value)` | Update value |
464
+ | `.set(path, fn)` | Functional update |
465
+ | `.reset(path)` | Delete value at path |
466
+ | `.subscribe(path, fn)` | Subscribe to changes (for effects) |
467
+ | `.notify(path)` | Manually trigger subscribers |
468
+ | `.useCompute(path, fn)` | Derive a computed value |
469
+ | `.Render({ path, children })` | Render prop component |
470
+ | `.Show({ path, children, on })` | Conditional render component |
471
+
453
472
  ### State Methods
454
473
 
455
- | Method | Description |
456
- | ------------------------ | ------------------------------------------------------- |
457
- | `.use()` | Subscribe and read value (triggers re-render on change) |
458
- | `.useDebounce(ms)` | Subscribe with debounced updates |
459
- | `.useState()` | Returns `[value, setValue]` tuple |
460
- | `.value` | Read without subscribing |
461
- | `.set(value)` | Update value |
462
- | `.set(fn)` | Functional update |
463
- | `.reset()` | Delete value at path |
464
- | `.subscribe(fn)` | Subscribe to changes (for effects) |
465
- | `.notify()` | Manually trigger subscribers |
466
- | `.compute(fn)` | Derive a computed value |
467
- | `.derived({ from, to })` | Create bidirectional transform |
468
- | `.Render` | Render prop component |
469
- | `.Show` | Conditional render component |
474
+ | Method | Description |
475
+ | ------------------------- | ------------------------------------------------------- |
476
+ | `.use()` | Subscribe and read value (triggers re-render on change) |
477
+ | `.useDebounce(ms)` | Subscribe with debounced updates |
478
+ | `.useState()` | Returns `[value, setValue]` tuple |
479
+ | `.value` | Read without subscribing |
480
+ | `.set(value)` | Update value |
481
+ | `.set(fn)` | Functional update |
482
+ | `.reset()` | Delete value at path |
483
+ | `.subscribe(fn)` | Subscribe to changes (for effects) |
484
+ | `.notify()` | Manually trigger subscribers |
485
+ | `.useCompute(fn)` | Derive a computed value |
486
+ | `.derived({ from, to })` | Create bidirectional transform |
487
+ | `.Render({ children })` | Render prop component |
488
+ | `.Show({ children, on })` | Conditional render component |
470
489
 
471
490
  ## License
472
491
 
package/dist/impl.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues } from './path';
2
- export { getNestedValue, getSnapshot, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe };
2
+ export { getNestedValue, getSnapshot, isClass, isEqual, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe };
3
+ declare function isClass(value: unknown): boolean;
4
+ /** Compare two values for equality
5
+ * @description
6
+ * - react-fast-compare for non-class instances
7
+ * - reference equality for class instances
8
+ * @param a - The first value to compare
9
+ * @param b - The second value to compare
10
+ * @returns True if the values are equal, false otherwise
11
+ */
12
+ declare function isEqual(a: unknown, b: unknown): boolean;
3
13
  /**
4
14
  * Joins a namespace and path into a full key string.
5
15
  *
package/dist/impl.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
- import isEqual from 'react-fast-compare';
2
+ import rfcIsEqual from 'react-fast-compare';
3
3
  import { localStorageDelete, localStorageGet, localStorageSet } from './local_storage';
4
- export { getNestedValue, getSnapshot, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe };
4
+ export { getNestedValue, getSnapshot, isClass, isEqual, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe };
5
5
  const memoryStore = new Map();
6
6
  // check if the value is a class instance
7
7
  function isClass(value) {
@@ -19,6 +19,19 @@ function isClass(value) {
19
19
  }
20
20
  return false;
21
21
  }
22
+ /** Compare two values for equality
23
+ * @description
24
+ * - react-fast-compare for non-class instances
25
+ * - reference equality for class instances
26
+ * @param a - The first value to compare
27
+ * @param b - The second value to compare
28
+ * @returns True if the values are equal, false otherwise
29
+ */
30
+ function isEqual(a, b) {
31
+ if (isClass(a) || isClass(b))
32
+ return a === b;
33
+ return rfcIsEqual(a, b);
34
+ }
22
35
  /**
23
36
  * Joins a namespace and path into a full key string.
24
37
  *
@@ -375,8 +388,7 @@ function produce(key, value, skipUpdate = false, memoryOnly = false) {
375
388
  store.delete(key, memoryOnly);
376
389
  }
377
390
  else {
378
- const useRefEquality = isClass(current) || isClass(value);
379
- if (useRefEquality ? current === value : isEqual(current, value))
391
+ if (isEqual(current, value))
380
392
  return;
381
393
  store.set(key, value, memoryOnly);
382
394
  }
package/dist/node.js CHANGED
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { useState } from 'react';
3
1
  export { createNode, createRootNode };
4
2
  /**
5
3
  * Creates the root proxy node for dynamic path access.
@@ -70,12 +68,9 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
70
68
  if (prop === 'Show') {
71
69
  return ({ children, on }) => storeApi.Show({ path, children, on: value => on(from(value)) });
72
70
  }
73
- if (prop === 'compute') {
71
+ if (prop === 'useCompute') {
74
72
  return (fn) => {
75
- const initialValue = from(storeApi.value(path));
76
- const [computedValue, setComputedValue] = useState(() => fn(initialValue));
77
- storeApi.subscribe(path, value => setComputedValue(fn(from(value))));
78
- return computedValue;
73
+ return storeApi.useCompute(path, value => fn(from(value)));
79
74
  };
80
75
  }
81
76
  if (prop === 'derived') {
package/dist/root.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useCallback, useRef } from 'react';
2
- import { getNestedValue, getSnapshot, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe } from './impl';
1
+ import { useCallback, useRef, useState } from 'react';
2
+ import { getNestedValue, getSnapshot, isEqual, joinPath, notifyListeners, produce, setLeaf, useDebounce, useObject, useSubscribe } from './impl';
3
3
  export { createStoreRoot };
4
4
  /**
5
5
  * Creates the core store API with path-based methods.
@@ -34,6 +34,17 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
34
34
  subscribe: (path, listener) =>
35
35
  // eslint-disable-next-line react-hooks/rules-of-hooks
36
36
  useSubscribe(joinPath(namespace, path), listener),
37
+ useCompute: (path, fn) => {
38
+ const initialValue = getSnapshot(joinPath(namespace, path));
39
+ const [computedValue, setComputedValue] = useState(() => fn(initialValue));
40
+ useSubscribe(path, value => {
41
+ const newValue = fn(value);
42
+ if (!isEqual(computedValue, newValue)) {
43
+ setComputedValue(newValue);
44
+ }
45
+ });
46
+ return computedValue;
47
+ },
37
48
  notify: (path) => {
38
49
  const value = getNestedValue(getSnapshot(namespace), path);
39
50
  return notifyListeners(joinPath(namespace, path), value, value, true, true);
package/dist/types.d.ts CHANGED
@@ -43,6 +43,8 @@ type StoreRoot<T extends FieldValues> = {
43
43
  reset: <P extends FieldPath<T>>(path: P) => void;
44
44
  /** Subscribe to changes at path and invoke listener with the new value. */
45
45
  subscribe: <P extends FieldPath<T>>(path: P, listener: (value: FieldPathValue<T, P>) => void) => void;
46
+ /** Compute a derived value from the current value, similar to useState + useMemo */
47
+ useCompute: <P extends FieldPath<T>>(path: P, fn: (value: FieldPathValue<T, P>) => FieldPathValue<T, P>) => FieldPathValue<T, P>;
46
48
  /** Notify listeners at path. */
47
49
  notify: <P extends FieldPath<T>>(path: P) => void;
48
50
  /** Convenience hook returning [value, setValue] for the path. */
@@ -71,7 +73,7 @@ type State<T> = {
71
73
  /** Subscribe to changes at path and invoke listener with the new value. */
72
74
  subscribe(listener: (value: T) => void): void;
73
75
  /** Compute a derived value from the current value, similar to useState + useMemo */
74
- compute: <R>(fn: (value: T) => R) => R;
76
+ useCompute: <R>(fn: (value: T) => R) => R;
75
77
  /** Virtual state derived from the current value. */
76
78
  derived: <R>({ from, to }: {
77
79
  from: (value: T | undefined) => R;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "description": "A small, expressive, and type-safe state management library for React.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,7 +8,7 @@
8
8
  "author": "Yusing",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/yusing/juststore"
11
+ "url": "git+https://github.com/yusing/juststore.git"
12
12
  },
13
13
  "homepage": "https://github.com/yusing/juststore",
14
14
  "bugs": {