@tanstack/store 0.0.1-beta.90 → 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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/store",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.90",
4
+ "version": "0.1.0",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/store",
7
7
  "homepage": "https://tanstack.com/store",
@@ -17,19 +17,35 @@
17
17
  "type": "github",
18
18
  "url": "https://github.com/sponsors/tannerlinsley"
19
19
  },
20
- "module": "build/esm/index.js",
21
- "main": "build/cjs/index.js",
22
- "browser": "build/umd/index.production.js",
23
- "types": "build/types/index.d.ts",
24
- "engines": {
25
- "node": ">=12"
20
+ "type": "module",
21
+ "types": "build/legacy/index.d.ts",
22
+ "main": "build/legacy/index.cjs",
23
+ "module": "build/legacy/index.js",
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./build/modern/index.d.ts",
28
+ "default": "./build/modern/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./build/modern/index.d.cts",
32
+ "default": "./build/modern/index.cjs"
33
+ }
34
+ },
35
+ "./package.json": "./package.json"
26
36
  },
37
+ "sideEffects": false,
27
38
  "files": [
28
- "build/**",
39
+ "build",
29
40
  "src"
30
41
  ],
31
- "sideEffects": false,
32
42
  "scripts": {
33
- "build": "rollup --config rollup.config.js"
43
+ "clean": "rimraf ./build && rimraf ./coverage",
44
+ "test:eslint": "eslint --ext .ts,.tsx ./src",
45
+ "test:types": "tsc",
46
+ "test:lib": "vitest run --coverage",
47
+ "test:lib:dev": "pnpm run test:lib --watch",
48
+ "test:build": "publint --strict",
49
+ "build": "tsup"
34
50
  }
35
51
  }
@@ -0,0 +1,83 @@
1
+ import { describe, test, expect, vi } from 'vitest'
2
+ import { Store } from '../index'
3
+
4
+ describe('store', () => {
5
+ test(`should set the initial value`, () => {
6
+ const store = new Store(0)
7
+
8
+ expect(store.state).toEqual(0)
9
+ })
10
+
11
+ test(`basic subscriptions should work`, () => {
12
+ const store = new Store(0)
13
+
14
+ const subscription = vi.fn()
15
+
16
+ const unsub = store.subscribe(subscription)
17
+
18
+ store.setState(() => 1)
19
+
20
+ expect(store.state).toEqual(1)
21
+ expect(subscription).toHaveBeenCalled()
22
+
23
+ unsub()
24
+
25
+ store.setState(() => 2)
26
+
27
+ expect(store.state).toEqual(2)
28
+
29
+ expect(subscription).toHaveBeenCalledTimes(1)
30
+ })
31
+
32
+ test(`setState passes previous state`, () => {
33
+ const store = new Store(3)
34
+
35
+ store.setState((v) => v + 1)
36
+
37
+ expect(store.state).toEqual(4)
38
+ })
39
+
40
+ test(`updateFn acts as state transformer`, () => {
41
+ const store = new Store(1, {
42
+ updateFn: (v) => (updater) => Number(updater(v)),
43
+ })
44
+
45
+ store.setState((v) => `${v + 1}` as never)
46
+
47
+ expect(store.state).toEqual(2)
48
+
49
+ store.setState((v) => `${v + 2}` as never)
50
+
51
+ expect(store.state).toEqual(4)
52
+
53
+ expect(typeof store.state).toEqual('number')
54
+ })
55
+
56
+ test('Batch prevents listeners from being called during repeated setStates', () => {
57
+ const store = new Store(0)
58
+
59
+ const listener = vi.fn()
60
+
61
+ store.subscribe(listener)
62
+
63
+ store.batch(() => {
64
+ store.setState(() => 1)
65
+ store.setState(() => 2)
66
+ store.setState(() => 3)
67
+ store.setState(() => 4)
68
+ })
69
+
70
+ expect(store.state).toEqual(4)
71
+ // Listener is only called once because of batching
72
+ expect(listener).toHaveBeenCalledTimes(1)
73
+
74
+ store.setState(() => 1)
75
+ store.setState(() => 2)
76
+ store.setState(() => 3)
77
+ store.setState(() => 4)
78
+
79
+ expect(store.state).toEqual(4)
80
+ // Listener is called 4 times because of a lack of batching
81
+ expect(listener).toHaveBeenCalledTimes(5)
82
+ })
83
+ })
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export type AnyUpdater = (...args: any[]) => any
2
2
 
3
- export type Listener = () => void
3
+ export type Listener = (opts: { priority: Priority }) => void
4
+
5
+ export type Priority = 'high' | 'low'
4
6
 
5
7
  interface StoreOptions<
6
8
  TState,
@@ -11,7 +13,8 @@ interface StoreOptions<
11
13
  listener: Listener,
12
14
  store: Store<TState, TUpdater>,
13
15
  ) => () => void
14
- onUpdate?: () => void
16
+ onUpdate?: (opts: { priority: Priority }) => void
17
+ defaultPriority?: Priority
15
18
  }
16
19
 
17
20
  export class Store<
@@ -23,6 +26,7 @@ export class Store<
23
26
  options?: StoreOptions<TState, TUpdater>
24
27
  _batching = false
25
28
  _flushing = 0
29
+ _nextPriority: null | Priority = null
26
30
 
27
31
  constructor(initialState: TState, options?: StoreOptions<TState, TUpdater>) {
28
32
  this.state = initialState
@@ -38,14 +42,30 @@ export class Store<
38
42
  }
39
43
  }
40
44
 
41
- setState = (updater: TUpdater) => {
45
+ setState = (
46
+ updater: TUpdater,
47
+ opts?: {
48
+ priority: Priority
49
+ },
50
+ ) => {
42
51
  const previous = this.state
43
52
  this.state = this.options?.updateFn
44
53
  ? this.options.updateFn(previous)(updater)
45
54
  : (updater as any)(previous)
46
55
 
56
+ const priority = opts?.priority ?? this.options?.defaultPriority ?? 'high'
57
+ if (this._nextPriority === null) {
58
+ this._nextPriority = priority
59
+ } else if (this._nextPriority === 'high') {
60
+ this._nextPriority = priority
61
+ } else {
62
+ this._nextPriority = this.options?.defaultPriority ?? 'high'
63
+ }
64
+
47
65
  // Always run onUpdate, regardless of batching
48
- this.options?.onUpdate?.()
66
+ this.options?.onUpdate?.({
67
+ priority: this._nextPriority,
68
+ })
49
69
 
50
70
  // Attempt to flush
51
71
  this._flush()
@@ -56,7 +76,9 @@ export class Store<
56
76
  const flushId = ++this._flushing
57
77
  this.listeners.forEach((listener) => {
58
78
  if (this._flushing !== flushId) return
59
- listener()
79
+ listener({
80
+ priority: this._nextPriority ?? 'high',
81
+ })
60
82
  })
61
83
  }
62
84
 
@@ -1,59 +0,0 @@
1
- /**
2
- * store
3
- *
4
- * Copyright (c) TanStack
5
- *
6
- * This source code is licensed under the MIT license found in the
7
- * LICENSE.md file in the root directory of this source tree.
8
- *
9
- * @license MIT
10
- */
11
- 'use strict';
12
-
13
- Object.defineProperty(exports, '__esModule', { value: true });
14
-
15
- class Store {
16
- listeners = new Set();
17
- _batching = false;
18
- _flushing = 0;
19
- constructor(initialState, options) {
20
- this.state = initialState;
21
- this.options = options;
22
- }
23
- subscribe = listener => {
24
- this.listeners.add(listener);
25
- const unsub = this.options?.onSubscribe?.(listener, this);
26
- return () => {
27
- this.listeners.delete(listener);
28
- unsub?.();
29
- };
30
- };
31
- setState = updater => {
32
- const previous = this.state;
33
- this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
34
-
35
- // Always run onUpdate, regardless of batching
36
- this.options?.onUpdate?.();
37
-
38
- // Attempt to flush
39
- this._flush();
40
- };
41
- _flush = () => {
42
- if (this._batching) return;
43
- const flushId = ++this._flushing;
44
- this.listeners.forEach(listener => {
45
- if (this._flushing !== flushId) return;
46
- listener();
47
- });
48
- };
49
- batch = cb => {
50
- if (this._batching) return cb();
51
- this._batching = true;
52
- cb();
53
- this._batching = false;
54
- this._flush();
55
- };
56
- }
57
-
58
- exports.Store = Store;
59
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["export type AnyUpdater = (...args: any[]) => any\n\nexport type Listener = () => void\n\ninterface StoreOptions<\n TState,\n TUpdater extends AnyUpdater = (cb: TState) => TState,\n> {\n updateFn?: (previous: TState) => (updater: TUpdater) => TState\n onSubscribe?: (\n listener: Listener,\n store: Store<TState, TUpdater>,\n ) => () => void\n onUpdate?: () => void\n}\n\nexport class Store<\n TState,\n TUpdater extends AnyUpdater = (cb: TState) => TState,\n> {\n listeners = new Set<Listener>()\n state: TState\n options?: StoreOptions<TState, TUpdater>\n _batching = false\n _flushing = 0\n\n constructor(initialState: TState, options?: StoreOptions<TState, TUpdater>) {\n this.state = initialState\n this.options = options\n }\n\n subscribe = (listener: Listener) => {\n this.listeners.add(listener)\n const unsub = this.options?.onSubscribe?.(listener, this)\n return () => {\n this.listeners.delete(listener)\n unsub?.()\n }\n }\n\n setState = (updater: TUpdater) => {\n const previous = this.state\n this.state = this.options?.updateFn\n ? this.options.updateFn(previous)(updater)\n : (updater as any)(previous)\n\n // Always run onUpdate, regardless of batching\n this.options?.onUpdate?.()\n\n // Attempt to flush\n this._flush()\n }\n\n _flush = () => {\n if (this._batching) return\n const flushId = ++this._flushing\n this.listeners.forEach((listener) => {\n if (this._flushing !== flushId) return\n listener()\n })\n }\n\n batch = (cb: () => void) => {\n if (this._batching) return cb()\n this._batching = true\n cb()\n this._batching = false\n this._flush()\n }\n}\n"],"names":["Store","listeners","Set","_batching","_flushing","constructor","initialState","options","state","subscribe","listener","add","unsub","onSubscribe","delete","setState","updater","previous","updateFn","onUpdate","_flush","flushId","forEach","batch","cb"],"mappings":";;;;;;;;;;;;;;AAgBO,MAAMA,KAAK,CAGhB;EACAC,SAAS,GAAG,IAAIC,GAAG,EAAY,CAAA;AAG/BC,EAAAA,SAAS,GAAG,KAAK,CAAA;AACjBC,EAAAA,SAAS,GAAG,CAAC,CAAA;AAEbC,EAAAA,WAAW,CAACC,YAAoB,EAAEC,OAAwC,EAAE;IAC1E,IAAI,CAACC,KAAK,GAAGF,YAAY,CAAA;IACzB,IAAI,CAACC,OAAO,GAAGA,OAAO,CAAA;AACxB,GAAA;EAEAE,SAAS,GAAIC,QAAkB,IAAK;AAClC,IAAA,IAAI,CAACT,SAAS,CAACU,GAAG,CAACD,QAAQ,CAAC,CAAA;IAC5B,MAAME,KAAK,GAAG,IAAI,CAACL,OAAO,EAAEM,WAAW,GAAGH,QAAQ,EAAE,IAAI,CAAC,CAAA;AACzD,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,CAACT,SAAS,CAACa,MAAM,CAACJ,QAAQ,CAAC,CAAA;AAC/BE,MAAAA,KAAK,IAAI,CAAA;KACV,CAAA;GACF,CAAA;EAEDG,QAAQ,GAAIC,OAAiB,IAAK;AAChC,IAAA,MAAMC,QAAQ,GAAG,IAAI,CAACT,KAAK,CAAA;IAC3B,IAAI,CAACA,KAAK,GAAG,IAAI,CAACD,OAAO,EAAEW,QAAQ,GAC/B,IAAI,CAACX,OAAO,CAACW,QAAQ,CAACD,QAAQ,CAAC,CAACD,OAAO,CAAC,GACvCA,OAAO,CAASC,QAAQ,CAAC,CAAA;;AAE9B;AACA,IAAA,IAAI,CAACV,OAAO,EAAEY,QAAQ,IAAI,CAAA;;AAE1B;IACA,IAAI,CAACC,MAAM,EAAE,CAAA;GACd,CAAA;AAEDA,EAAAA,MAAM,GAAG,MAAM;IACb,IAAI,IAAI,CAACjB,SAAS,EAAE,OAAA;AACpB,IAAA,MAAMkB,OAAO,GAAG,EAAE,IAAI,CAACjB,SAAS,CAAA;AAChC,IAAA,IAAI,CAACH,SAAS,CAACqB,OAAO,CAAEZ,QAAQ,IAAK;AACnC,MAAA,IAAI,IAAI,CAACN,SAAS,KAAKiB,OAAO,EAAE,OAAA;AAChCX,MAAAA,QAAQ,EAAE,CAAA;AACZ,KAAC,CAAC,CAAA;GACH,CAAA;EAEDa,KAAK,GAAIC,EAAc,IAAK;AAC1B,IAAA,IAAI,IAAI,CAACrB,SAAS,EAAE,OAAOqB,EAAE,EAAE,CAAA;IAC/B,IAAI,CAACrB,SAAS,GAAG,IAAI,CAAA;AACrBqB,IAAAA,EAAE,EAAE,CAAA;IACJ,IAAI,CAACrB,SAAS,GAAG,KAAK,CAAA;IACtB,IAAI,CAACiB,MAAM,EAAE,CAAA;GACd,CAAA;AACH;;;;"}
@@ -1,55 +0,0 @@
1
- /**
2
- * store
3
- *
4
- * Copyright (c) TanStack
5
- *
6
- * This source code is licensed under the MIT license found in the
7
- * LICENSE.md file in the root directory of this source tree.
8
- *
9
- * @license MIT
10
- */
11
- class Store {
12
- listeners = new Set();
13
- _batching = false;
14
- _flushing = 0;
15
- constructor(initialState, options) {
16
- this.state = initialState;
17
- this.options = options;
18
- }
19
- subscribe = listener => {
20
- this.listeners.add(listener);
21
- const unsub = this.options?.onSubscribe?.(listener, this);
22
- return () => {
23
- this.listeners.delete(listener);
24
- unsub?.();
25
- };
26
- };
27
- setState = updater => {
28
- const previous = this.state;
29
- this.state = this.options?.updateFn ? this.options.updateFn(previous)(updater) : updater(previous);
30
-
31
- // Always run onUpdate, regardless of batching
32
- this.options?.onUpdate?.();
33
-
34
- // Attempt to flush
35
- this._flush();
36
- };
37
- _flush = () => {
38
- if (this._batching) return;
39
- const flushId = ++this._flushing;
40
- this.listeners.forEach(listener => {
41
- if (this._flushing !== flushId) return;
42
- listener();
43
- });
44
- };
45
- batch = cb => {
46
- if (this._batching) return cb();
47
- this._batching = true;
48
- cb();
49
- this._batching = false;
50
- this._flush();
51
- };
52
- }
53
-
54
- export { Store };
55
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["export type AnyUpdater = (...args: any[]) => any\n\nexport type Listener = () => void\n\ninterface StoreOptions<\n TState,\n TUpdater extends AnyUpdater = (cb: TState) => TState,\n> {\n updateFn?: (previous: TState) => (updater: TUpdater) => TState\n onSubscribe?: (\n listener: Listener,\n store: Store<TState, TUpdater>,\n ) => () => void\n onUpdate?: () => void\n}\n\nexport class Store<\n TState,\n TUpdater extends AnyUpdater = (cb: TState) => TState,\n> {\n listeners = new Set<Listener>()\n state: TState\n options?: StoreOptions<TState, TUpdater>\n _batching = false\n _flushing = 0\n\n constructor(initialState: TState, options?: StoreOptions<TState, TUpdater>) {\n this.state = initialState\n this.options = options\n }\n\n subscribe = (listener: Listener) => {\n this.listeners.add(listener)\n const unsub = this.options?.onSubscribe?.(listener, this)\n return () => {\n this.listeners.delete(listener)\n unsub?.()\n }\n }\n\n setState = (updater: TUpdater) => {\n const previous = this.state\n this.state = this.options?.updateFn\n ? this.options.updateFn(previous)(updater)\n : (updater as any)(previous)\n\n // Always run onUpdate, regardless of batching\n this.options?.onUpdate?.()\n\n // Attempt to flush\n this._flush()\n }\n\n _flush = () => {\n if (this._batching) return\n const flushId = ++this._flushing\n this.listeners.forEach((listener) => {\n if (this._flushing !== flushId) return\n listener()\n })\n }\n\n batch = (cb: () => void) => {\n if (this._batching) return cb()\n this._batching = true\n cb()\n this._batching = false\n this._flush()\n }\n}\n"],"names":["Store","listeners","Set","_batching","_flushing","constructor","initialState","options","state","subscribe","listener","add","unsub","onSubscribe","delete","setState","updater","previous","updateFn","onUpdate","_flush","flushId","forEach","batch","cb"],"mappings":";;;;;;;;;;AAgBO,MAAMA,KAAK,CAGhB;EACAC,SAAS,GAAG,IAAIC,GAAG,EAAY,CAAA;AAG/BC,EAAAA,SAAS,GAAG,KAAK,CAAA;AACjBC,EAAAA,SAAS,GAAG,CAAC,CAAA;AAEbC,EAAAA,WAAW,CAACC,YAAoB,EAAEC,OAAwC,EAAE;IAC1E,IAAI,CAACC,KAAK,GAAGF,YAAY,CAAA;IACzB,IAAI,CAACC,OAAO,GAAGA,OAAO,CAAA;AACxB,GAAA;EAEAE,SAAS,GAAIC,QAAkB,IAAK;AAClC,IAAA,IAAI,CAACT,SAAS,CAACU,GAAG,CAACD,QAAQ,CAAC,CAAA;IAC5B,MAAME,KAAK,GAAG,IAAI,CAACL,OAAO,EAAEM,WAAW,GAAGH,QAAQ,EAAE,IAAI,CAAC,CAAA;AACzD,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,CAACT,SAAS,CAACa,MAAM,CAACJ,QAAQ,CAAC,CAAA;AAC/BE,MAAAA,KAAK,IAAI,CAAA;KACV,CAAA;GACF,CAAA;EAEDG,QAAQ,GAAIC,OAAiB,IAAK;AAChC,IAAA,MAAMC,QAAQ,GAAG,IAAI,CAACT,KAAK,CAAA;IAC3B,IAAI,CAACA,KAAK,GAAG,IAAI,CAACD,OAAO,EAAEW,QAAQ,GAC/B,IAAI,CAACX,OAAO,CAACW,QAAQ,CAACD,QAAQ,CAAC,CAACD,OAAO,CAAC,GACvCA,OAAO,CAASC,QAAQ,CAAC,CAAA;;AAE9B;AACA,IAAA,IAAI,CAACV,OAAO,EAAEY,QAAQ,IAAI,CAAA;;AAE1B;IACA,IAAI,CAACC,MAAM,EAAE,CAAA;GACd,CAAA;AAEDA,EAAAA,MAAM,GAAG,MAAM;IACb,IAAI,IAAI,CAACjB,SAAS,EAAE,OAAA;AACpB,IAAA,MAAMkB,OAAO,GAAG,EAAE,IAAI,CAACjB,SAAS,CAAA;AAChC,IAAA,IAAI,CAACH,SAAS,CAACqB,OAAO,CAAEZ,QAAQ,IAAK;AACnC,MAAA,IAAI,IAAI,CAACN,SAAS,KAAKiB,OAAO,EAAE,OAAA;AAChCX,MAAAA,QAAQ,EAAE,CAAA;AACZ,KAAC,CAAC,CAAA;GACH,CAAA;EAEDa,KAAK,GAAIC,EAAc,IAAK;AAC1B,IAAA,IAAI,IAAI,CAACrB,SAAS,EAAE,OAAOqB,EAAE,EAAE,CAAA;IAC/B,IAAI,CAACrB,SAAS,GAAG,IAAI,CAAA;AACrBqB,IAAAA,EAAE,EAAE,CAAA;IACJ,IAAI,CAACrB,SAAS,GAAG,KAAK,CAAA;IACtB,IAAI,CAACiB,MAAM,EAAE,CAAA;GACd,CAAA;AACH;;;;"}