juststore 0.4.3 → 0.4.4

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/dist/impl.js CHANGED
@@ -84,7 +84,7 @@ function getNamespace(key) {
84
84
  function joinPath(namespace, path) {
85
85
  if (!path)
86
86
  return namespace;
87
- return namespace + '.' + path;
87
+ return `${namespace}.${path}`;
88
88
  }
89
89
  function joinChildKey(parent, child) {
90
90
  return parent ? `${parent}.${child}` : child;
@@ -93,16 +93,16 @@ function getKeyPrefixes(key) {
93
93
  const dot = key.indexOf('.');
94
94
  if (dot === -1)
95
95
  return [];
96
- const parts = key.split('.');
97
- if (parts.length <= 1)
96
+ const [first, ...parts] = key.split('.');
97
+ if (parts.length === 0)
98
98
  return [];
99
99
  const prefixes = [];
100
- let current = parts[0];
101
- for (let i = 1; i < parts.length - 1; i++) {
102
- current += '.' + parts[i];
100
+ let current = first;
101
+ for (let i = 0; i < parts.length - 1; i++) {
102
+ current += `.${parts[i]}`;
103
103
  prefixes.push(current);
104
104
  }
105
- prefixes.unshift(parts[0]);
105
+ prefixes.unshift(first);
106
106
  return prefixes;
107
107
  }
108
108
  /** Snapshot getter used by React's useSyncExternalStore. */
@@ -260,7 +260,7 @@ function setNestedValue(obj, path, value) {
260
260
  }
261
261
  else if (typeof current === 'object' && current !== null) {
262
262
  const currentObj = current;
263
- const hadKey = Object.prototype.hasOwnProperty.call(currentObj, lastSegment);
263
+ const hadKey = Object.hasOwn(currentObj, lastSegment);
264
264
  if (value === undefined) {
265
265
  delete currentObj[lastSegment];
266
266
  if (hadKey) {
@@ -316,21 +316,29 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
316
316
  // exact match only
317
317
  const listenerSet = listeners.get(key);
318
318
  if (listenerSet) {
319
- listenerSet.forEach(listener => listener());
319
+ listenerSet.forEach(listener => {
320
+ listener();
321
+ });
320
322
  }
321
323
  return;
322
324
  }
323
325
  // Exact key match
324
326
  const exactSet = listeners.get(key);
325
327
  if (exactSet) {
326
- exactSet.forEach(listener => listener());
328
+ exactSet.forEach(listener => {
329
+ listener();
330
+ });
327
331
  }
328
332
  // Ancestor keys match (including namespace root)
329
333
  if (!skipRoot) {
330
334
  const namespace = getNamespace(key);
331
- const rootSet = listeners.get(namespace);
332
- if (rootSet) {
333
- rootSet.forEach(listener => listener());
335
+ if (namespace !== key) {
336
+ const rootSet = listeners.get(namespace);
337
+ if (rootSet) {
338
+ rootSet.forEach(listener => {
339
+ listener();
340
+ });
341
+ }
334
342
  }
335
343
  // Also notify intermediate ancestors
336
344
  const prefixes = getKeyPrefixes(key);
@@ -339,7 +347,9 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
339
347
  continue; // Already handled
340
348
  const prefixSet = listeners.get(prefix);
341
349
  if (prefixSet) {
342
- prefixSet.forEach(listener => listener());
350
+ prefixSet.forEach(listener => {
351
+ listener();
352
+ });
343
353
  }
344
354
  }
345
355
  }
@@ -369,7 +379,9 @@ function notifyListeners(key, oldValue, newValue, { skipRoot = false, skipChildr
369
379
  if (forceNotify || !isEqual(oldChildValue, newChildValue)) {
370
380
  const childSet = listeners.get(childKey);
371
381
  if (childSet) {
372
- childSet.forEach(listener => listener());
382
+ childSet.forEach(listener => {
383
+ listener();
384
+ });
373
385
  }
374
386
  }
375
387
  }
@@ -395,13 +407,13 @@ function subscribe(key, listener) {
395
407
  if (!listeners.has(key)) {
396
408
  listeners.set(key, new Set());
397
409
  }
398
- listeners.get(key).add(listener);
410
+ listeners.get(key)?.add(listener);
399
411
  const prefixes = getKeyPrefixes(key);
400
412
  for (const prefix of prefixes) {
401
413
  if (!descendantListenerKeysByPrefix.has(prefix)) {
402
414
  descendantListenerKeysByPrefix.set(prefix, new Set());
403
415
  }
404
- descendantListenerKeysByPrefix.get(prefix).add(key);
416
+ descendantListenerKeysByPrefix.get(prefix)?.add(key);
405
417
  }
406
418
  return () => {
407
419
  const keyListeners = listeners.get(key);
@@ -471,12 +483,12 @@ function rename(path, oldKey, newKey, memoryOnly) {
471
483
  const obj = current;
472
484
  if (oldKey === newKey)
473
485
  return;
474
- if (!Object.prototype.hasOwnProperty.call(obj, oldKey))
486
+ if (!Object.hasOwn(obj, oldKey))
475
487
  return;
476
488
  const keyOrder = getStableKeys(obj);
477
489
  const entries = [];
478
490
  for (const key of keyOrder) {
479
- if (!Object.prototype.hasOwnProperty.call(obj, key))
491
+ if (!Object.hasOwn(obj, key))
480
492
  continue;
481
493
  if (key === oldKey) {
482
494
  entries.push([newKey, obj[oldKey]]);
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { createAtom, type Atom } from './atom';
1
+ export { type Atom, createAtom } from './atom';
2
2
  export type * from './form';
3
3
  export { useForm } from './form';
4
4
  export { isEqual } from './impl';
5
- export { createMemoryStore, useMemoryStore, type MemoryStore } from './memory';
5
+ export { createMemoryStore, type MemoryStore, useMemoryStore } from './memory';
6
6
  export { createMixedState } from './mixed_state';
7
7
  export type * from './path';
8
8
  export { createStore, type Store } from './store';
@@ -33,7 +33,9 @@ function createMixedState(...states) {
33
33
  useEffect(() => {
34
34
  const unsubscribeFns = states.map(state => state.subscribe(recompute));
35
35
  return () => {
36
- unsubscribeFns.forEach(unsubscribe => unsubscribe());
36
+ unsubscribeFns.forEach(unsubscribe => {
37
+ unsubscribe();
38
+ });
37
39
  };
38
40
  });
39
41
  return value;
package/dist/node.js CHANGED
@@ -1,4 +1,5 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: <T is not known at this point> */
2
+ /** biome-ignore-all lint/suspicious/noAssignInExpressions: <getter methods are cached> */
2
3
  import { getStableKeys } from './impl';
3
4
  export { createNode, createRootNode };
4
5
  /**
@@ -350,7 +351,6 @@ function createKeysNode(storeApi, path, getObjectValue) {
350
351
  return (_target._Render ??= ({ children }) => children(storeApi.useCompute(signalPath, computeKeys), () => { }));
351
352
  }
352
353
  if (prop === 'Show') {
353
- // eslint-disable-next-line react/display-name
354
354
  return (_target._Show ??= ({ children, on }) => {
355
355
  const show = storeApi.useCompute(signalPath, () => on(computeKeys()), [on]);
356
356
  return show ? children : null;
package/dist/path.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: <Intended> */
1
2
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
2
3
  export type BrowserNativeObject = Date | FileList | File;
3
4
  /**
package/dist/root.js CHANGED
@@ -15,6 +15,7 @@ export { createStoreRoot };
15
15
  * @returns A proxy object providing both path-based and dynamic property access to the store
16
16
  */
17
17
  function createStoreRoot(namespace, defaultValue, options = {}) {
18
+ 'use memo';
18
19
  const memoryOnly = options?.memoryOnly ?? false;
19
20
  // merge with default value and save in memory only
20
21
  produce(namespace, { ...defaultValue, ...(getSnapshot(namespace, memoryOnly) ?? {}) }, true, true);
@@ -45,16 +46,21 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
45
46
  const fnRef = useRef(fn);
46
47
  fnRef.current = fn;
47
48
  const cacheRef = useRef(null);
49
+ const depsRef = useRef(deps);
50
+ // Invalidate cached compute when hook inputs change.
51
+ if (!isEqual(depsRef.current, deps)) {
52
+ depsRef.current = deps;
53
+ cacheRef.current = null;
54
+ }
55
+ const pathRef = useRef(fullPath);
56
+ if (pathRef.current !== fullPath) {
57
+ pathRef.current = fullPath;
58
+ cacheRef.current = null;
59
+ }
48
60
  const subscribeToPath = useCallback((onStoreChange) => subscribe(fullPath, onStoreChange), [fullPath]);
49
61
  const getComputedSnapshot = useCallback(() => {
50
- if (cacheRef.current && cacheRef.current.path !== fullPath) {
51
- cacheRef.current = null;
52
- }
53
- if (cacheRef.current && !isEqual(cacheRef.current.deps, deps)) {
54
- cacheRef.current = null;
55
- }
56
62
  const storeValue = getSnapshot(fullPath, memoryOnly);
57
- if (cacheRef.current && isEqual(cacheRef.current.storeValue, storeValue)) {
63
+ if (cacheRef.current && Object.is(cacheRef.current.storeValue, storeValue)) {
58
64
  // same store value, return the same computed value
59
65
  return cacheRef.current.computed;
60
66
  }
@@ -66,9 +72,9 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
66
72
  cacheRef.current.storeValue = storeValue;
67
73
  return cacheRef.current.computed;
68
74
  }
69
- cacheRef.current = { path: fullPath, storeValue, computed: computedNext, deps };
75
+ cacheRef.current = { storeValue, computed: computedNext };
70
76
  return computedNext;
71
- }, [fullPath, deps]);
77
+ }, [fullPath]);
72
78
  return useSyncExternalStore(subscribeToPath, getComputedSnapshot, getComputedSnapshot);
73
79
  },
74
80
  notify: (path) => {
@@ -80,31 +86,19 @@ function createStoreRoot(namespace, defaultValue, options = {}) {
80
86
  });
81
87
  },
82
88
  useState: (path) => {
83
- const fullPath = joinPath(namespace, path);
84
89
  const setValue = useCallback((value) => {
85
- if (typeof value === 'function') {
86
- const currentValue = getSnapshot(fullPath, memoryOnly);
87
- const newValue = value(currentValue);
88
- return setLeaf(namespace, path, newValue, false, memoryOnly);
89
- }
90
- return setLeaf(namespace, path, value, false, memoryOnly);
91
- }, [fullPath, path]);
90
+ storeApi.set(path, value, false);
91
+ }, [path]);
92
92
  return [
93
93
  useObject(namespace, path, memoryOnly),
94
94
  setValue
95
95
  ];
96
96
  },
97
97
  Render: ({ path, children }) => {
98
- const fullPath = joinPath(namespace, path);
99
98
  const value = useObject(namespace, path, memoryOnly);
100
99
  const update = useCallback((value) => {
101
- if (typeof value === 'function') {
102
- const currentValue = getSnapshot(fullPath, memoryOnly);
103
- const newValue = value(currentValue);
104
- return setLeaf(namespace, path, newValue, false, memoryOnly);
105
- }
106
- return setLeaf(namespace, path, value, false, memoryOnly);
107
- }, [fullPath, path]);
100
+ storeApi.set(path, value, false);
101
+ }, [path]);
108
102
  return children(value, update);
109
103
  },
110
104
  Show: ({ path, children, on }) => {
@@ -13,7 +13,7 @@ function getStableKeys(value) {
13
13
  const target = value;
14
14
  const existing = externalKeyOrder.get(target);
15
15
  if (existing) {
16
- const next = existing.filter(k => Object.prototype.hasOwnProperty.call(target, k));
16
+ const next = existing.filter(k => Object.hasOwn(target, k));
17
17
  const nextSet = new Set(next);
18
18
  for (const k of Object.keys(target)) {
19
19
  if (nextSet.has(k))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "A small, expressive, and type-safe state management library for React.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,11 +35,11 @@
35
35
  "react hooks"
36
36
  ],
37
37
  "scripts": {
38
- "build": "bun run tsc",
39
- "typecheck": "bun run tsc --noEmit",
40
- "lint": "bun run eslint src/**/*.ts",
41
- "format": "bun run prettier --write src/**/*.ts",
42
- "format:check": "bun run prettier --check src/**/*.ts",
38
+ "build": "bun --bun tsc",
39
+ "typecheck": "bun --bun tsc --noEmit",
40
+ "lint": "bun --bun biome",
41
+ "format": "bun --bun biome format --write",
42
+ "format:check": "bun --bun biome format",
43
43
  "prepublishOnly": "bun run build",
44
44
  "publish": "npm publish --access public",
45
45
  "prepare": "husky"
@@ -52,18 +52,11 @@
52
52
  "react-fast-compare": "^3.2.2"
53
53
  },
54
54
  "devDependencies": {
55
+ "@biomejs/biome": "^2.3.14",
55
56
  "@eslint/js": "^9.39.2",
56
57
  "@types/node": "^24.10.4",
57
58
  "@types/react": "^19.2.7",
58
- "@typescript-eslint/eslint-plugin": "^8.50.1",
59
- "@typescript-eslint/parser": "^8.50.1",
60
- "eslint": "^9.39.2",
61
- "eslint-plugin-prettier": "^5.5.4",
62
- "eslint-plugin-react": "^7.37.5",
63
- "eslint-plugin-react-hooks": "^7.0.1",
64
- "eslint-plugin-react-refresh": "^0.4.26",
65
59
  "husky": "^9.1.7",
66
- "prettier": "^3.7.4",
67
60
  "react": "^19.2.3",
68
61
  "typescript": "^5.9.3"
69
62
  }