@dvashim/store 1.4.6 → 1.5.1

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
@@ -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. Re-entrant calls from within a subscriber are queued and flushed in FIFO order. A safety limit of 100 re-entrant updates prevents infinite loops.
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)
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 are queued and flushed in FIFO order.
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
  */
@@ -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;AASrC;;;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;IAKrC;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;CAgEpD"}
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.#queue.push({ updater: () => state, options });
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 are queued and flushed in FIFO order.
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.#queue.push({ updater, options });
46
- return this.#flush();
42
+ this.#notify(updater(this.#state), options);
47
43
  }
48
- #flush() {
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
- while (iterations < this.#queue.length) {
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
- const state = q.updater(this.#state);
64
- this.#commit(state, q.options);
61
+ listener(nextState, prevState);
65
62
  }
66
63
  catch (error) {
67
- if (firstError === NO_ERROR) {
68
- firstError = error;
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":"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"}
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/package.json CHANGED
@@ -4,27 +4,28 @@
4
4
  "name": "@dvashim/store",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
- "version": "1.4.6",
7
+ "version": "1.5.1",
8
8
  "author": {
9
9
  "email": "aleksei@dvashim.dev",
10
10
  "name": "Aleksei Reznichenko"
11
11
  },
12
12
  "devDependencies": {
13
- "@biomejs/biome": "^2.4.6",
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.2",
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.3.5",
19
+ "@types/node": "^25.5.0",
20
20
  "@types/react": "^19.2.14",
21
21
  "@types/react-dom": "^19.2.3",
22
+ "@vitest/coverage-v8": "^4.1.0",
22
23
  "jsdom": "^28.1.0",
23
24
  "react": "^19.2.4",
24
25
  "react-dom": "^19.2.4",
25
26
  "rimraf": "^6.1.3",
26
27
  "typescript": "^5.9.3",
27
- "vitest": "^4.0.18"
28
+ "vitest": "^4.1.0"
28
29
  },
29
30
  "exports": {
30
31
  ".": {
@@ -66,6 +67,7 @@
66
67
  "ci": "pnpm check && pnpm test",
67
68
  "clean": "rm -rf dist *.tsbuildinfo",
68
69
  "test": "vitest run",
70
+ "test:coverage": "vitest run --coverage",
69
71
  "test:watch": "vitest",
70
72
  "watch": "pnpm build --watch"
71
73
  }