juststore 0.0.5 → 0.0.7

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
@@ -46,6 +46,121 @@ const store = createStore<AppState>('app', {
46
46
  })
47
47
  ```
48
48
 
49
+ ## Real-World Examples (GoDoxy Web UI)
50
+
51
+ ### Homepage navigation and search
52
+
53
+ ```tsx
54
+ import { store } from '@/components/home/store'
55
+
56
+ function HomepageFilters() {
57
+ const categories = store.homepageCategories.use()
58
+ const [activeCategory, setActiveCategory] = store.navigation.activeCategory.useState()
59
+ const query = store.searchQuery.useDebounce(150)
60
+
61
+ const visibleItems =
62
+ categories
63
+ .find(cat => cat.name === activeCategory)
64
+ ?.items.filter(item => item.name.toLowerCase().includes((query ?? '').toLowerCase())) ?? []
65
+
66
+ return (
67
+ <div>
68
+ <input
69
+ value={query ?? ''}
70
+ onChange={e => store.searchQuery.set(e.target.value)}
71
+ placeholder="Search services"
72
+ />
73
+ <div>
74
+ {categories.map(name => (
75
+ <button
76
+ key={name}
77
+ data-active={name === activeCategory}
78
+ onClick={() => setActiveCategory(name)}
79
+ >
80
+ {name}
81
+ </button>
82
+ ))}
83
+ </div>
84
+ <ul>
85
+ {visibleItems.map(item => (
86
+ <li key={item.name}>{item.name}</li>
87
+ ))}
88
+ </ul>
89
+ </div>
90
+ )
91
+ }
92
+ ```
93
+
94
+ ### Live route uptime sidebar
95
+
96
+ ```tsx
97
+ import { useWebSocketApi } from '@/hooks/websocket'
98
+ import type { RouteKey } from '@/components/routes/store'
99
+ import { store } from '@/components/routes/store'
100
+ import type { RouteUptimeAggregate, UptimeAggregate } from '@/lib/api'
101
+
102
+ function RoutesUptimeProvider() {
103
+ useWebSocketApi<UptimeAggregate>({
104
+ endpoint: '/metrics/uptime',
105
+ query: { period: '1d' },
106
+ onMessage: uptime => {
107
+ const keys = uptime.data.map(route => route.alias as RouteKey)
108
+ store.set('routeKeys', keys.toSorted())
109
+ store.set(
110
+ 'uptime',
111
+ keys.reduce(
112
+ (acc, key, index) => {
113
+ acc[key] = uptime.data[index] as RouteUptimeAggregate
114
+ return acc
115
+ },
116
+ {} as Record<RouteKey, RouteUptimeAggregate>
117
+ )
118
+ )
119
+ }
120
+ })
121
+
122
+ return null
123
+ }
124
+ ```
125
+
126
+ ### Server metrics via WebSockets
127
+
128
+ ```tsx
129
+ import { useWebSocketApi } from '@/hooks/websocket'
130
+ import { store } from '@/components/servers/store'
131
+ import type { MetricsPeriod, SystemInfoAggregate, SystemInfoAggregateMode } from '@/lib/api'
132
+
133
+ const MODES: SystemInfoAggregateMode[] = [
134
+ 'cpu_average',
135
+ 'memory_usage',
136
+ 'disks_read_speed',
137
+ 'disks_write_speed',
138
+ 'disks_iops',
139
+ 'disk_usage',
140
+ 'network_speed',
141
+ 'network_transfer',
142
+ 'sensor_temperature'
143
+ ]
144
+
145
+ function SystemInfoGraphsProvider({ agent, period }: { agent: string; period: MetricsPeriod }) {
146
+ MODES.forEach(mode => {
147
+ useWebSocketApi<SystemInfoAggregate>({
148
+ endpoint: '/metrics/system_info',
149
+ query: {
150
+ period,
151
+ aggregate: mode,
152
+ agent_name: agent === 'Main Server' ? '' : agent
153
+ },
154
+ onMessage: data => {
155
+ store.systemInfoGraphs[agent]?.[period]?.[mode]?.set(data)
156
+ }
157
+ })
158
+ })
159
+
160
+ return null
161
+ }
162
+ ```
163
+
49
164
  ## Usage
50
165
 
51
166
  ### Reading State
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,8 +1,37 @@
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
+ // check if the value is a class instance
7
+ function isClass(value) {
8
+ if (value === null || value === undefined)
9
+ return false;
10
+ if (typeof value !== 'object')
11
+ return false;
12
+ const proto = Object.getPrototypeOf(value);
13
+ if (!proto || proto === Object.prototype || proto === Array.prototype)
14
+ return false;
15
+ const descriptors = Object.getOwnPropertyDescriptors(proto);
16
+ for (const key in descriptors) {
17
+ if (descriptors[key].get)
18
+ return true;
19
+ }
20
+ return false;
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
+ }
6
35
  /**
7
36
  * Joins a namespace and path into a full key string.
8
37
  *
@@ -83,7 +112,7 @@ function setNestedValue(obj, path, value) {
83
112
  if (!path)
84
113
  return value;
85
114
  const segments = path.split('.');
86
- const result = tryStructuredClone(obj);
115
+ const result = segments.length === 1 ? obj : tryStructuredClone(obj);
87
116
  let current = result ?? {};
88
117
  for (let i = 0; i < segments.length - 1; i++) {
89
118
  const segment = segments[i];
package/dist/node.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { useState } from 'react';
3
+ import { isEqual } from './impl';
3
4
  export { createNode, createRootNode };
4
5
  /**
5
6
  * Creates the root proxy node for dynamic path access.
@@ -74,7 +75,12 @@ function createNode(storeApi, path, cache, extensions, from = unchanged, to = un
74
75
  return (fn) => {
75
76
  const initialValue = from(storeApi.value(path));
76
77
  const [computedValue, setComputedValue] = useState(() => fn(initialValue));
77
- storeApi.subscribe(path, value => setComputedValue(fn(from(value))));
78
+ storeApi.subscribe(path, value => {
79
+ const newValue = fn(from(value));
80
+ if (!isEqual(computedValue, newValue)) {
81
+ setComputedValue(newValue);
82
+ }
83
+ });
78
84
  return computedValue;
79
85
  };
80
86
  }
package/dist/path.js CHANGED
@@ -1,2 +1,26 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /*
2
+ Copied and modified from https://github.com/react-hook-form/react-hook-form
3
+
4
+ MIT License
5
+
6
+ Copyright (c) 2019-present Beier(Bill) Luo
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
25
+ */
2
26
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juststore",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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": {
@@ -24,7 +24,7 @@
24
24
  "files": [
25
25
  "dist"
26
26
  ],
27
- "license": "MIT",
27
+ "license": "AGPL-3.0-only",
28
28
  "keywords": [
29
29
  "state",
30
30
  "react",