@dvashim/store 1.4.5 → 1.5.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 +7 -4
- package/dist/Store.d.ts +1 -1
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +20 -43
- package/dist/Store.js.map +1 -1
- package/dist/useStore.d.ts +5 -6
- package/dist/useStore.d.ts.map +1 -1
- package/dist/useStore.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -78,9 +78,7 @@ count$.set(5, { force: true })
|
|
|
78
78
|
|
|
79
79
|
#### `store.update(updater, options?)`
|
|
80
80
|
|
|
81
|
-
Derives the next state via an updater function.
|
|
82
|
-
|
|
83
|
-
If an updater throws, remaining queued items are still processed and the first error is rethrown after the queue drains.
|
|
81
|
+
Derives the next state via an updater function. Calling `set()` or `update()` from within a subscriber throws an error.
|
|
84
82
|
|
|
85
83
|
```ts
|
|
86
84
|
count$.update((n) => n + 1)
|
|
@@ -148,13 +146,18 @@ remaining$.isConnected // true
|
|
|
148
146
|
|
|
149
147
|
### `useStore(store, selector?)`
|
|
150
148
|
|
|
151
|
-
React hook that subscribes a component to
|
|
149
|
+
React hook that subscribes a component to any `SourceStore` — works with both `Store` and `ComputedStore`.
|
|
152
150
|
|
|
153
151
|
```tsx
|
|
154
152
|
function Counter() {
|
|
155
153
|
const count = useStore(count$)
|
|
156
154
|
return <p>{count}</p>
|
|
157
155
|
}
|
|
156
|
+
|
|
157
|
+
function Remaining() {
|
|
158
|
+
const remaining = useStore(remaining$)
|
|
159
|
+
return <p>{remaining} left</p>
|
|
160
|
+
}
|
|
158
161
|
```
|
|
159
162
|
|
|
160
163
|
#### With a selector
|
package/dist/Store.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export declare class Store<T> implements SourceStore<T> {
|
|
|
24
24
|
set(state: T, options?: UpdateOptions): void;
|
|
25
25
|
/**
|
|
26
26
|
* Derives the next state via an updater function and notifies subscribers.
|
|
27
|
-
* Re-entrant calls from within a subscriber
|
|
27
|
+
* Re-entrant calls from within a subscriber throw an error.
|
|
28
28
|
* @param updater - Receives the current state and returns the next state.
|
|
29
29
|
* @param options - Pass `{ force: true }` to notify even when unchanged.
|
|
30
30
|
*/
|
package/dist/Store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAErE,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAErE,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAA;AAErC;;;GAGG;AACH,qBAAa,KAAK,CAAC,CAAC,CAAE,YAAW,WAAW,CAAC,CAAC,CAAC;;gBAMjC,YAAY,EAAE,CAAC;IAI3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAKxC,iCAAiC;IACjC,GAAG,IAAI,CAAC;IAIR;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;IAIrC;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;CA0CpD"}
|
package/dist/Store.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
const NO_ERROR = Symbol('no error');
|
|
2
|
-
const MAX_FLUSH_ITERATIONS = 100;
|
|
3
1
|
/**
|
|
4
2
|
* Reactive state container with subscription-based change notification.
|
|
5
3
|
* @typeParam T - The type of the stored state.
|
|
6
4
|
*/
|
|
7
5
|
export class Store {
|
|
8
6
|
#subscribers = new Set();
|
|
9
|
-
#queue = [];
|
|
10
7
|
#notifying = false;
|
|
8
|
+
#reentrantError = null;
|
|
11
9
|
#state;
|
|
12
10
|
constructor(initialState) {
|
|
13
11
|
this.#state = initialState;
|
|
@@ -32,68 +30,47 @@ export class Store {
|
|
|
32
30
|
* @param options - Pass `{ force: true }` to notify even when unchanged.
|
|
33
31
|
*/
|
|
34
32
|
set(state, options) {
|
|
35
|
-
this.#
|
|
36
|
-
return this.#flush();
|
|
33
|
+
this.#notify(state, options);
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
39
36
|
* Derives the next state via an updater function and notifies subscribers.
|
|
40
|
-
* Re-entrant calls from within a subscriber
|
|
37
|
+
* Re-entrant calls from within a subscriber throw an error.
|
|
41
38
|
* @param updater - Receives the current state and returns the next state.
|
|
42
39
|
* @param options - Pass `{ force: true }` to notify even when unchanged.
|
|
43
40
|
*/
|
|
44
41
|
update(updater, options) {
|
|
45
|
-
this.#
|
|
46
|
-
return this.#flush();
|
|
42
|
+
this.#notify(updater(this.#state), options);
|
|
47
43
|
}
|
|
48
|
-
#
|
|
44
|
+
#notify(nextState, options) {
|
|
49
45
|
if (this.#notifying) {
|
|
46
|
+
this.#reentrantError = new Error('set() or update() cannot be called from within a subscriber');
|
|
47
|
+
throw this.#reentrantError;
|
|
48
|
+
}
|
|
49
|
+
if (!options?.force && Object.is(nextState, this.#state)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const prevState = this.#state;
|
|
53
|
+
this.#state = nextState;
|
|
54
|
+
if (this.#subscribers.size === 0) {
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
52
57
|
this.#notifying = true;
|
|
53
|
-
let iterations = 0;
|
|
54
|
-
let firstError = NO_ERROR;
|
|
55
58
|
try {
|
|
56
|
-
|
|
57
|
-
if (iterations >= MAX_FLUSH_ITERATIONS) {
|
|
58
|
-
throw new Error(`Store: exceeded ${MAX_FLUSH_ITERATIONS} re-entrant updates. This likely indicates an infinite loop in a subscriber.`);
|
|
59
|
-
}
|
|
60
|
-
const q = this.#queue[iterations];
|
|
61
|
-
iterations += 1;
|
|
59
|
+
for (const listener of [...this.#subscribers]) {
|
|
62
60
|
try {
|
|
63
|
-
|
|
64
|
-
this.#commit(state, q.options);
|
|
61
|
+
listener(nextState, prevState);
|
|
65
62
|
}
|
|
66
63
|
catch (error) {
|
|
67
|
-
if (
|
|
68
|
-
|
|
64
|
+
if (error === this.#reentrantError) {
|
|
65
|
+
throw error;
|
|
69
66
|
}
|
|
67
|
+
console.error('Error in subscriber', error);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
finally {
|
|
74
|
-
this.#queue.length = 0;
|
|
75
72
|
this.#notifying = false;
|
|
76
|
-
|
|
77
|
-
if (firstError !== NO_ERROR) {
|
|
78
|
-
throw firstError;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
#commit(nextState, options) {
|
|
82
|
-
if (!options?.force && Object.is(nextState, this.#state)) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const prevState = this.#state;
|
|
86
|
-
this.#state = nextState;
|
|
87
|
-
if (this.#subscribers.size === 0) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
for (const listener of [...this.#subscribers]) {
|
|
91
|
-
try {
|
|
92
|
-
listener(nextState, prevState);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
console.error('Error in subscriber', error);
|
|
96
|
-
}
|
|
73
|
+
this.#reentrantError = null;
|
|
97
74
|
}
|
|
98
75
|
}
|
|
99
76
|
}
|
package/dist/Store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Store.js","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Store.js","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,OAAO,KAAK;IACP,YAAY,GAAG,IAAI,GAAG,EAAiB,CAAA;IAChD,UAAU,GAAG,KAAK,CAAA;IAClB,eAAe,GAAiB,IAAI,CAAA;IACpC,MAAM,CAAG;IAET,YAAY,YAAe;QACzB,IAAI,CAAC,MAAM,GAAG,YAAY,CAAA;IAC5B,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,EAAiB;QACzB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACzB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED,iCAAiC;IACjC,GAAG;QACD,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,KAAQ,EAAE,OAAuB;QACnC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC9B,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,OAAmB,EAAE,OAAuB;QACjD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAA;IAC7C,CAAC;IAED,OAAO,CAAC,SAAY,EAAE,OAAuB;QAC3C,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,eAAe,GAAG,IAAI,KAAK,CAC9B,6DAA6D,CAC9D,CAAA;YAED,MAAM,IAAI,CAAC,eAAe,CAAA;QAC5B,CAAC;QAED,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,OAAM;QACR,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QAEvB,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACjC,OAAM;QACR,CAAC;QAED,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QAEtB,IAAI,CAAC;YACH,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC9C,IAAI,CAAC;oBACH,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;gBAChC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,KAAK,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;wBACnC,MAAM,KAAK,CAAA;oBACb,CAAC;oBACD,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAA;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;YACvB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC7B,CAAC;IACH,CAAC;CACF"}
|
package/dist/useStore.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Selector } from './types';
|
|
1
|
+
import type { Selector, SourceStore } from './types';
|
|
3
2
|
/**
|
|
4
|
-
* Subscribes a React component to a {@link
|
|
3
|
+
* Subscribes a React component to a {@link SourceStore} and returns its current state.
|
|
5
4
|
*
|
|
6
5
|
* @param store - The store to subscribe to.
|
|
7
6
|
* @returns The current state of the store.
|
|
8
7
|
*/
|
|
9
|
-
declare function useStore<T>(store:
|
|
8
|
+
declare function useStore<T>(store: SourceStore<T>): T;
|
|
10
9
|
/**
|
|
11
|
-
* Subscribes a React component to a {@link
|
|
10
|
+
* Subscribes a React component to a {@link SourceStore} and returns a derived value
|
|
12
11
|
* computed by the selector.
|
|
13
12
|
*
|
|
14
13
|
* The selector should return a referentially stable value (e.g. a primitive or
|
|
@@ -19,6 +18,6 @@ declare function useStore<T>(store: Store<T>): T;
|
|
|
19
18
|
* @param selector - A function that derives a value from the store state.
|
|
20
19
|
* @returns The value returned by the selector.
|
|
21
20
|
*/
|
|
22
|
-
declare function useStore<T, U>(store:
|
|
21
|
+
declare function useStore<T, U>(store: SourceStore<T>, selector: Selector<T, U>): U;
|
|
23
22
|
export { useStore };
|
|
24
23
|
//# sourceMappingURL=useStore.d.ts.map
|
package/dist/useStore.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"useStore.d.ts","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAEpD;;;;;GAKG;AACH,iBAAS,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAE9C;;;;;;;;;;;GAWG;AACH,iBAAS,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;AA6B3E,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
package/dist/useStore.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStore.js","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"useStore.js","sourceRoot":"","sources":["../src/useStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAyB5E,SAAS,QAAQ,CACf,KAAqB,EACrB,QAAyB;IAEzB,MAAM,WAAW,GAAG,MAAM,CAAkB,SAAS,CAAC,CAAA;IACtD,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE;QACxB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAE7C,MAAM,WAAW,GAAG,GAAG,EAAE,CACvB,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QAEtE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAA;IACnC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,MAAM,KAAK,GAAG,oBAAoB,CAChC,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,WAAW,CACjB,CAAA;IAED,aAAa,CAAC,KAAK,CAAC,CAAA;IAEpB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
"name": "@dvashim/store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.5.0",
|
|
8
8
|
"author": {
|
|
9
9
|
"email": "aleksei@dvashim.dev",
|
|
10
10
|
"name": "Aleksei Reznichenko"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@biomejs/biome": "^2.4.
|
|
13
|
+
"@biomejs/biome": "^2.4.7",
|
|
14
14
|
"@changesets/changelog-github": "^0.6.0",
|
|
15
15
|
"@changesets/cli": "^2.30.0",
|
|
16
|
-
"@dvashim/biome-config": "^1.3.
|
|
16
|
+
"@dvashim/biome-config": "^1.3.3",
|
|
17
17
|
"@dvashim/typescript-config": "^1.1.12",
|
|
18
18
|
"@testing-library/react": "^16.3.2",
|
|
19
|
-
"@types/node": "^25.
|
|
19
|
+
"@types/node": "^25.5.0",
|
|
20
20
|
"@types/react": "^19.2.14",
|
|
21
21
|
"@types/react-dom": "^19.2.3",
|
|
22
22
|
"jsdom": "^28.1.0",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"react-dom": "^19.2.4",
|
|
25
25
|
"rimraf": "^6.1.3",
|
|
26
26
|
"typescript": "^5.9.3",
|
|
27
|
-
"vitest": "^4.0
|
|
27
|
+
"vitest": "^4.1.0"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|