@dvashim/store 1.3.0 → 1.4.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 CHANGED
@@ -103,6 +103,52 @@ const unsubscribe = count$.subscribe((state, prevState) => {
103
103
  unsubscribe()
104
104
  ```
105
105
 
106
+ ### `ComputedStore`
107
+
108
+ A read-only reactive store that derives its value from a source store using a selector. Automatically updates when the source changes. Accepts any `SourceStore<T>` (including `Store` or another `ComputedStore`) as its source.
109
+
110
+ > **Note:** `ComputedStore` is not yet re-exported from the barrel. Import it directly:
111
+ >
112
+ > ```ts
113
+ > import { ComputedStore } from '@dvashim/store/ComputedStore'
114
+ > ```
115
+
116
+ ```ts
117
+ const todos$ = createStore([
118
+ { text: 'Buy milk', done: true },
119
+ { text: 'Walk dog', done: false },
120
+ ])
121
+
122
+ const remaining$ = new ComputedStore(todos$, (todos) =>
123
+ todos.filter((t) => !t.done).length
124
+ )
125
+
126
+ remaining$.get() // 1
127
+ remaining$.subscribe((count, prev) => console.log(`${prev} → ${count}`))
128
+ ```
129
+
130
+ #### Chaining
131
+
132
+ `ComputedStore` implements `SourceStore<U>`, so it can be used as the source for another `ComputedStore`.
133
+
134
+ ```ts
135
+ const count$ = new ComputedStore(todos$, (todos) => todos.length)
136
+ const label$ = new ComputedStore(count$, (n) => `${n} items`)
137
+ label$.get() // "2 items"
138
+ ```
139
+
140
+ #### `computed.connect()` / `computed.disconnect()`
141
+
142
+ Control the subscription to the source store. After `disconnect()`, the derived value stops updating and `get()` returns the last known value. Call `connect()` to resume.
143
+
144
+ ```ts
145
+ remaining$.disconnect()
146
+ remaining$.isConnected // false
147
+
148
+ remaining$.connect()
149
+ remaining$.isConnected // true
150
+ ```
151
+
106
152
  ### `useStore(store, selector?)`
107
153
 
108
154
  React hook that subscribes a component to a store.
@@ -127,6 +173,21 @@ function UserName() {
127
173
  }
128
174
  ```
129
175
 
176
+ ### Types
177
+
178
+ The following types are exported from the package:
179
+
180
+ ```ts
181
+ import type { Selector, Subscriber, UpdateOptions, SourceStore } from '@dvashim/store'
182
+ ```
183
+
184
+ | Type | Definition |
185
+ | ---- | ---------- |
186
+ | `Selector<T, U>` | `(state: T) => U` |
187
+ | `Subscriber<T>` | `(state: T, prevState: T) => void` |
188
+ | `UpdateOptions` | `{ force?: boolean }` |
189
+ | `SourceStore<T>` | `{ get(): T; subscribe(fn: Subscriber<T>): () => void }` — shared interface implemented by both `Store` and `ComputedStore` |
190
+
130
191
  ## Patterns
131
192
 
132
193
  ### Shared stores across components
@@ -0,0 +1,13 @@
1
+ import type { Selector, SourceStore, Subscriber } from './types';
2
+ export declare class ComputedStore<T, U> implements SourceStore<U> {
3
+ #private;
4
+ constructor(source: SourceStore<T>, selector: Selector<T, U>);
5
+ connect(): void;
6
+ disconnect(): void;
7
+ get isConnected(): boolean;
8
+ protected get source(): SourceStore<T>;
9
+ protected get selector(): Selector<T, U>;
10
+ get(): U;
11
+ subscribe(fn: Subscriber<U>): () => void;
12
+ }
13
+ //# sourceMappingURL=ComputedStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComputedStore.d.ts","sourceRoot":"","sources":["../src/ComputedStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEhE,qBAAa,aAAa,CAAC,CAAC,EAAE,CAAC,CAAE,YAAW,WAAW,CAAC,CAAC,CAAC;;gBAM5C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IAO5D,OAAO;IAOP,UAAU;IAKV,IAAI,WAAW,YAEd;IAED,SAAS,KAAK,MAAM,mBAEnB;IAED,SAAS,KAAK,QAAQ,mBAErB;IAED,GAAG;IAIH,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;CAG5B"}
@@ -0,0 +1,52 @@
1
+ import { Store } from './Store';
2
+ export class ComputedStore {
3
+ #source;
4
+ #derived;
5
+ #selector;
6
+ #unsubscribe;
7
+ constructor(source, selector) {
8
+ this.#source = source;
9
+ this.#derived = new Store(selector(source.get()));
10
+ this.#selector = selector;
11
+ this.connect();
12
+ }
13
+ connect() {
14
+ this.disconnect();
15
+ this.#unsubscribe = this.#source.subscribe((state) => {
16
+ this.#derived.set(this.#selector(state));
17
+ });
18
+ }
19
+ disconnect() {
20
+ this.#unsubscribe?.();
21
+ this.#unsubscribe = undefined;
22
+ }
23
+ get isConnected() {
24
+ return !!this.#unsubscribe;
25
+ }
26
+ get source() {
27
+ return this.#source;
28
+ }
29
+ get selector() {
30
+ return this.#selector;
31
+ }
32
+ get() {
33
+ return this.#derived.get();
34
+ }
35
+ subscribe(fn) {
36
+ return this.#derived.subscribe(fn);
37
+ }
38
+ }
39
+ // const s = new Store([1, 2])
40
+ // const len = new ComputedStore(s, (state) => state.length)
41
+ // const lenString = new ComputedStore(len, (state) => `length: ${state}`)
42
+ // const sum = new ComputedStore(s, (state) => state.reduce((a, b) => a + b, 0))
43
+ // console.log(len.get()) // 2
44
+ // console.log(sum.get()) // 3
45
+ // console.log(lenString.get()) // length: 2
46
+ // const len1 = new SubStore(s, (state) => state.length)
47
+ // const lenString1 = new SubStore(len1, (state) => `length: ${state}`)
48
+ // const sum1 = new SubStore(s, (state) => state.reduce((a, b) => a + b, 0))
49
+ // console.log(len1.get()) // 2
50
+ // console.log(sum1.get()) // 3
51
+ // console.log(lenString1.get()) // length: 2
52
+ //# sourceMappingURL=ComputedStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComputedStore.js","sourceRoot":"","sources":["../src/ComputedStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAG/B,MAAM,OAAO,aAAa;IACf,OAAO,CAAgB;IACvB,QAAQ,CAAU;IAClB,SAAS,CAAgB;IAClC,YAAY,CAA2B;IAEvC,YAAY,MAAsB,EAAE,QAAwB;QAC1D,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACjD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QACzB,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAED,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;YACnD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,UAAU;QACR,IAAI,CAAC,YAAY,EAAE,EAAE,CAAA;QACrB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAA;IAC/B,CAAC;IAED,IAAI,WAAW;QACb,OAAO,CAAC,CAAC,IAAI,CAAC,YAAY,CAAA;IAC5B,CAAC;IAED,IAAc,MAAM;QAClB,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED,IAAc,QAAQ;QACpB,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAA;IAC5B,CAAC;IAED,SAAS,CAAC,EAAiB;QACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACpC,CAAC;CACF;AAED,8BAA8B;AAC9B,4DAA4D;AAC5D,0EAA0E;AAC1E,gFAAgF;AAChF,8BAA8B;AAC9B,8BAA8B;AAC9B,4CAA4C;AAE5C,wDAAwD;AACxD,uEAAuE;AACvE,4EAA4E;AAC5E,+BAA+B;AAC/B,+BAA+B;AAC/B,6CAA6C"}
package/dist/Store.d.ts CHANGED
@@ -1,13 +1,10 @@
1
- type Subscriber<T> = (state: T, prevState: T) => void;
1
+ import type { SourceStore, Subscriber, UpdateOptions } from './types';
2
2
  type Updater<T> = (prevValue: T) => T;
3
- type UpdateOptions = {
4
- force?: boolean;
5
- };
6
3
  /**
7
4
  * Reactive state container with subscription-based change notification.
8
5
  * @typeParam T - The type of the stored state.
9
6
  */
10
- export declare class Store<T> {
7
+ export declare class Store<T> implements SourceStore<T> {
11
8
  #private;
12
9
  constructor(initialState: T);
13
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Store.d.ts","sourceRoot":"","sources":["../src/Store.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,IAAI,CAAA;AACrD,KAAK,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAA;AACrC,KAAK,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AASxC;;;GAGG;AACH,qBAAa,KAAK,CAAC,CAAC;;gBAMN,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;CA8DpD"}
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;CA8DpD"}
package/dist/types.d.ts CHANGED
@@ -1,2 +1,10 @@
1
1
  export type Selector<T, U> = (state: T) => U;
2
+ export type Subscriber<T> = (state: T, prevState: T) => void;
3
+ export type UpdateOptions = {
4
+ force?: boolean;
5
+ };
6
+ export type SourceStore<T> = {
7
+ get: () => T;
8
+ subscribe: (fn: Subscriber<T>) => () => void;
9
+ };
2
10
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;AAC5C,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,KAAK,IAAI,CAAA;AAC5D,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAC/C,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,GAAG,EAAE,MAAM,CAAC,CAAA;IACZ,SAAS,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,CAAA;CAC7C,CAAA"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "@dvashim/store",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
- "version": "1.3.0",
7
+ "version": "1.4.0",
8
8
  "author": {
9
9
  "email": "aleksei@dvashim.dev",
10
10
  "name": "Aleksei Reznichenko"