@tanstack/store 0.8.1 → 0.9.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.
Files changed (64) hide show
  1. package/dist/cjs/alien.cjs +345 -0
  2. package/dist/cjs/alien.cjs.map +1 -0
  3. package/dist/cjs/alien.d.cts +57 -0
  4. package/dist/cjs/atom.cjs +222 -0
  5. package/dist/cjs/atom.cjs.map +1 -0
  6. package/dist/cjs/atom.d.cts +16 -0
  7. package/dist/cjs/batch.cjs +15 -0
  8. package/dist/cjs/batch.cjs.map +1 -0
  9. package/dist/cjs/batch.d.cts +1 -0
  10. package/dist/cjs/index.cjs +9 -12
  11. package/dist/cjs/index.cjs.map +1 -1
  12. package/dist/cjs/index.d.cts +3 -4
  13. package/dist/cjs/store.cjs +39 -29
  14. package/dist/cjs/store.cjs.map +1 -1
  15. package/dist/cjs/store.d.cts +18 -29
  16. package/dist/cjs/types.d.cts +47 -20
  17. package/dist/esm/alien.d.ts +57 -0
  18. package/dist/esm/alien.js +345 -0
  19. package/dist/esm/alien.js.map +1 -0
  20. package/dist/esm/atom.d.ts +16 -0
  21. package/dist/esm/atom.js +222 -0
  22. package/dist/esm/atom.js.map +1 -0
  23. package/dist/esm/batch.d.ts +1 -0
  24. package/dist/esm/batch.js +15 -0
  25. package/dist/esm/batch.js.map +1 -0
  26. package/dist/esm/index.d.ts +3 -4
  27. package/dist/esm/index.js +9 -12
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/store.d.ts +18 -29
  30. package/dist/esm/store.js +40 -30
  31. package/dist/esm/store.js.map +1 -1
  32. package/dist/esm/types.d.ts +47 -20
  33. package/package.json +6 -7
  34. package/src/alien.ts +654 -0
  35. package/src/atom.ts +298 -0
  36. package/src/batch.ts +12 -0
  37. package/src/index.ts +3 -4
  38. package/src/store.ts +58 -69
  39. package/src/types.ts +60 -24
  40. package/dist/cjs/derived.cjs +0 -117
  41. package/dist/cjs/derived.cjs.map +0 -1
  42. package/dist/cjs/derived.d.cts +0 -50
  43. package/dist/cjs/effect.cjs +0 -24
  44. package/dist/cjs/effect.cjs.map +0 -1
  45. package/dist/cjs/effect.d.cts +0 -18
  46. package/dist/cjs/scheduler.cjs +0 -109
  47. package/dist/cjs/scheduler.cjs.map +0 -1
  48. package/dist/cjs/scheduler.d.cts +0 -27
  49. package/dist/cjs/types.cjs +0 -7
  50. package/dist/cjs/types.cjs.map +0 -1
  51. package/dist/esm/derived.d.ts +0 -50
  52. package/dist/esm/derived.js +0 -117
  53. package/dist/esm/derived.js.map +0 -1
  54. package/dist/esm/effect.d.ts +0 -18
  55. package/dist/esm/effect.js +0 -24
  56. package/dist/esm/effect.js.map +0 -1
  57. package/dist/esm/scheduler.d.ts +0 -27
  58. package/dist/esm/scheduler.js +0 -109
  59. package/dist/esm/scheduler.js.map +0 -1
  60. package/dist/esm/types.js +0 -7
  61. package/dist/esm/types.js.map +0 -1
  62. package/src/derived.ts +0 -203
  63. package/src/effect.ts +0 -42
  64. package/src/scheduler.ts +0 -155
package/src/atom.ts ADDED
@@ -0,0 +1,298 @@
1
+ import { ReactiveFlags, createReactiveSystem, getBatchDepth } from './alien'
2
+
3
+ import type { ReactiveNode } from './alien'
4
+ import type {
5
+ Atom,
6
+ AtomOptions,
7
+ Observer,
8
+ ReadonlyAtom,
9
+ Subscription,
10
+ } from './types'
11
+
12
+ export function toObserver<T>(
13
+ nextHandler?: Observer<T> | ((value: T) => void),
14
+ errorHandler?: (error: any) => void,
15
+ completionHandler?: () => void,
16
+ ): Observer<T> {
17
+ const isObserver = typeof nextHandler === 'object'
18
+ const self = isObserver ? nextHandler : undefined
19
+
20
+ return {
21
+ next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
22
+ error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
23
+ complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(
24
+ self,
25
+ ),
26
+ }
27
+ }
28
+
29
+ interface InternalAtom<T> extends ReactiveNode {
30
+ _snapshot: T
31
+ _update: (getValue?: T | ((snapshot: T) => T)) => boolean
32
+ get: () => T
33
+ subscribe: (observerOrFn: Observer<T> | ((value: T) => void)) => Subscription
34
+ }
35
+
36
+ const queuedEffects: Array<Effect | undefined> = []
37
+ let cycle = 0
38
+ const { link, unlink, propagate, checkDirty, shallowPropagate } =
39
+ createReactiveSystem({
40
+ update(atom: InternalAtom<any>): boolean {
41
+ return atom._update()
42
+ },
43
+ // eslint-disable-next-line no-shadow
44
+ notify(effect: Effect): void {
45
+ queuedEffects[queuedEffectsLength++] = effect
46
+ effect.flags &= ~ReactiveFlags.Watching
47
+ },
48
+ unwatched(atom: InternalAtom<any>): void {
49
+ if (atom.depsTail !== undefined) {
50
+ atom.depsTail = undefined
51
+ atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty
52
+ purgeDeps(atom)
53
+ }
54
+ },
55
+ })
56
+
57
+ let notifyIndex = 0
58
+ let queuedEffectsLength = 0
59
+ let activeSub: ReactiveNode | undefined
60
+
61
+ function purgeDeps(sub: ReactiveNode) {
62
+ const depsTail = sub.depsTail
63
+ let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps
64
+ while (dep !== undefined) {
65
+ dep = unlink(dep, sub)
66
+ }
67
+ }
68
+
69
+ export function flush(): void {
70
+ if (getBatchDepth() > 0) {
71
+ return
72
+ }
73
+ while (notifyIndex < queuedEffectsLength) {
74
+ // eslint-disable-next-line no-shadow
75
+ const effect = queuedEffects[notifyIndex]!
76
+ queuedEffects[notifyIndex++] = undefined
77
+ effect.notify()
78
+ }
79
+ notifyIndex = 0
80
+ queuedEffectsLength = 0
81
+ }
82
+
83
+ type AsyncAtomState<TData, TError = unknown> =
84
+ | { status: 'pending' }
85
+ | { status: 'done'; data: TData }
86
+ | { status: 'error'; error: TError }
87
+
88
+ export function createAsyncAtom<T>(
89
+ getValue: () => Promise<T>,
90
+ options?: AtomOptions<AsyncAtomState<T>>,
91
+ ): ReadonlyAtom<AsyncAtomState<T>> {
92
+ const ref: { current?: InternalAtom<AsyncAtomState<T>> } = {}
93
+ const atom = createAtom<AsyncAtomState<T>>(() => {
94
+ getValue().then(
95
+ (data) => {
96
+ const internalAtom = ref.current!
97
+ if (internalAtom._update({ status: 'done', data })) {
98
+ const subs = internalAtom.subs
99
+ if (subs !== undefined) {
100
+ propagate(subs)
101
+ shallowPropagate(subs)
102
+ flush()
103
+ }
104
+ }
105
+ },
106
+ (error) => {
107
+ const internalAtom = ref.current!
108
+ if (internalAtom._update({ status: 'error', error })) {
109
+ const subs = internalAtom.subs
110
+ if (subs !== undefined) {
111
+ propagate(subs)
112
+ shallowPropagate(subs)
113
+ flush()
114
+ }
115
+ }
116
+ },
117
+ )
118
+
119
+ return { status: 'pending' }
120
+ }, options)
121
+ ref.current = atom as unknown as InternalAtom<AsyncAtomState<T>>
122
+
123
+ return atom
124
+ }
125
+
126
+ export function createAtom<T>(
127
+ getValue: (prev?: NoInfer<T>) => T,
128
+ options?: AtomOptions<T>,
129
+ ): ReadonlyAtom<T>
130
+ export function createAtom<T>(
131
+ initialValue: T,
132
+ options?: AtomOptions<T>,
133
+ ): Atom<T>
134
+ export function createAtom<T>(
135
+ valueOrFn: T | ((prev?: T) => T),
136
+ options?: AtomOptions<T>,
137
+ ): Atom<T> | ReadonlyAtom<T> {
138
+ const isComputed = typeof valueOrFn === 'function'
139
+ const getter = valueOrFn as (prev?: T) => T
140
+
141
+ // Create plain object atom
142
+ const atom: InternalAtom<T> = {
143
+ _snapshot: isComputed ? undefined! : valueOrFn,
144
+
145
+ subs: undefined,
146
+ subsTail: undefined,
147
+ deps: undefined,
148
+ depsTail: undefined,
149
+ flags: isComputed ? ReactiveFlags.None : ReactiveFlags.Mutable,
150
+
151
+ get(): T {
152
+ if (activeSub !== undefined) {
153
+ link(atom, activeSub, cycle)
154
+ }
155
+ return atom._snapshot
156
+ },
157
+
158
+ subscribe(observerOrFn: Observer<T> | ((value: T) => void)) {
159
+ const obs = toObserver(observerOrFn)
160
+ const observed = { current: false }
161
+ const e = effect(() => {
162
+ atom.get()
163
+ if (!observed.current) {
164
+ observed.current = true
165
+ } else {
166
+ obs.next?.(atom._snapshot)
167
+ }
168
+ })
169
+
170
+ return {
171
+ unsubscribe: () => {
172
+ e.stop()
173
+ },
174
+ }
175
+ },
176
+ _update(getValue?: T | ((snapshot: T) => T)): boolean {
177
+ const prevSub = activeSub
178
+ const compare = options?.compare ?? Object.is
179
+ activeSub = atom
180
+ ++cycle
181
+ atom.depsTail = undefined
182
+ if (isComputed) {
183
+ atom.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck
184
+ }
185
+ try {
186
+ const oldValue = atom._snapshot
187
+ const newValue =
188
+ typeof getValue === 'function'
189
+ ? (getValue as (snapshot: T) => T)(oldValue)
190
+ : getValue === undefined && isComputed
191
+ ? getter(oldValue)
192
+ : getValue!
193
+ if (oldValue === undefined || !compare(oldValue, newValue)) {
194
+ atom._snapshot = newValue
195
+ return true
196
+ }
197
+ return false
198
+ } finally {
199
+ activeSub = prevSub
200
+ if (isComputed) {
201
+ atom.flags &= ~ReactiveFlags.RecursedCheck
202
+ }
203
+ purgeDeps(atom)
204
+ }
205
+ },
206
+ }
207
+
208
+ if (isComputed) {
209
+ atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty
210
+ atom.get = function (): T {
211
+ const flags = atom.flags
212
+ if (
213
+ flags & ReactiveFlags.Dirty ||
214
+ (flags & ReactiveFlags.Pending && checkDirty(atom.deps!, atom))
215
+ ) {
216
+ if (atom._update()) {
217
+ const subs = atom.subs
218
+ if (subs !== undefined) {
219
+ shallowPropagate(subs)
220
+ }
221
+ }
222
+ } else if (flags & ReactiveFlags.Pending) {
223
+ atom.flags = flags & ~ReactiveFlags.Pending
224
+ }
225
+ if (activeSub !== undefined) {
226
+ link(atom, activeSub, cycle)
227
+ }
228
+ return atom._snapshot
229
+ }
230
+ } else {
231
+ ;(atom as unknown as Atom<T>).set = function (
232
+ // eslint-disable-next-line no-shadow
233
+ valueOrFn: T | ((prev: T) => T),
234
+ ): void {
235
+ if (atom._update(valueOrFn)) {
236
+ const subs = atom.subs
237
+ if (subs !== undefined) {
238
+ propagate(subs)
239
+ shallowPropagate(subs)
240
+ flush()
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ return atom as unknown as Atom<T> | ReadonlyAtom<T>
247
+ }
248
+
249
+ interface Effect extends ReactiveNode {
250
+ notify: () => void
251
+ stop: () => void
252
+ }
253
+
254
+ function effect<T>(fn: () => T): Effect {
255
+ const run = (): T => {
256
+ const prevSub = activeSub
257
+ activeSub = effectObj
258
+ ++cycle
259
+ effectObj.depsTail = undefined
260
+ effectObj.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck
261
+ try {
262
+ return fn()
263
+ } finally {
264
+ activeSub = prevSub
265
+ effectObj.flags &= ~ReactiveFlags.RecursedCheck
266
+ purgeDeps(effectObj)
267
+ }
268
+ }
269
+ const effectObj: Effect = {
270
+ deps: undefined,
271
+ depsTail: undefined,
272
+ subs: undefined,
273
+ subsTail: undefined,
274
+ flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck,
275
+
276
+ notify(): void {
277
+ const flags = this.flags
278
+ if (
279
+ flags & ReactiveFlags.Dirty ||
280
+ (flags & ReactiveFlags.Pending && checkDirty(this.deps!, this))
281
+ ) {
282
+ run()
283
+ } else {
284
+ this.flags = ReactiveFlags.Watching
285
+ }
286
+ },
287
+
288
+ stop(): void {
289
+ this.flags = ReactiveFlags.None
290
+ this.depsTail = undefined
291
+ purgeDeps(this)
292
+ },
293
+ }
294
+
295
+ run()
296
+
297
+ return effectObj
298
+ }
package/src/batch.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { endBatch, startBatch } from './alien'
2
+ import { flush } from './atom'
3
+
4
+ export function batch(fn: () => void) {
5
+ try {
6
+ startBatch()
7
+ fn()
8
+ } finally {
9
+ endBatch()
10
+ flush()
11
+ }
12
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
- export * from './derived'
2
- export * from './effect'
3
- export * from './store'
4
1
  export * from './types'
5
- export * from './scheduler'
2
+ export * from './atom'
3
+ export * from './store'
4
+ export * from './batch'
package/src/store.ts CHANGED
@@ -1,77 +1,66 @@
1
- import { __flush } from './scheduler'
2
- import { isUpdaterFunction } from './types'
3
- import type { AnyUpdater, Listener, Updater } from './types'
1
+ import { createAtom, toObserver } from './atom'
2
+ import type { Atom, Observer, Subscription } from './types'
4
3
 
5
- export interface StoreOptions<
6
- TState,
7
- TUpdater extends AnyUpdater = (cb: TState) => TState,
8
- > {
9
- /**
10
- * Replace the default update function with a custom one.
11
- */
12
- updateFn?: (previous: TState) => (updater: TUpdater) => TState
13
- /**
14
- * Called when a listener subscribes to the store.
15
- *
16
- * @return a function to unsubscribe the listener
17
- */
18
- onSubscribe?: (
19
- listener: Listener<TState>,
20
- store: Store<TState, TUpdater>,
21
- ) => () => void
22
- /**
23
- * Called after the state has been updated, used to derive other state.
24
- */
25
- onUpdate?: () => void
4
+ export class Store<T> {
5
+ private atom: Atom<T>
6
+ constructor(getValue: (prev?: NoInfer<T>) => T)
7
+ constructor(initialValue: T)
8
+ constructor(valueOrFn: T | ((prev?: T) => T)) {
9
+ // createAtom has overloads that return ReadonlyAtom<T> for functions and Atom<T> for values
10
+ // Store always needs Atom<T> for setState, so we assert the return type
11
+ this.atom = createAtom(
12
+ valueOrFn as T | ((prev?: NoInfer<T>) => T),
13
+ ) as Atom<T>
14
+ }
15
+ public setState(updater: (prev: T) => T) {
16
+ this.atom.set(updater)
17
+ }
18
+ public get state() {
19
+ return this.atom.get()
20
+ }
21
+ public get() {
22
+ return this.state
23
+ }
24
+ public subscribe(
25
+ observerOrFn: Observer<T> | ((value: T) => void),
26
+ ): Subscription {
27
+ return this.atom.subscribe(toObserver(observerOrFn))
28
+ }
26
29
  }
27
30
 
28
- export class Store<
29
- TState,
30
- TUpdater extends AnyUpdater = (cb: TState) => TState,
31
- > {
32
- listeners = new Set<Listener<TState>>()
33
- state: TState
34
- prevState: TState
35
- options?: StoreOptions<TState, TUpdater>
36
-
37
- constructor(initialState: TState, options?: StoreOptions<TState, TUpdater>) {
38
- this.prevState = initialState
39
- this.state = initialState
40
- this.options = options
31
+ export class ReadonlyStore<T> implements Omit<Store<T>, 'setState'> {
32
+ private atom: Atom<T>
33
+ constructor(getValue: (prev?: NoInfer<T>) => T)
34
+ constructor(initialValue: T)
35
+ constructor(valueOrFn: T | ((prev?: T) => T)) {
36
+ // createAtom has overloads that return ReadonlyAtom<T> for functions and Atom<T> for values
37
+ // Store always needs Atom<T> for setState, so we assert the return type
38
+ this.atom = createAtom(
39
+ valueOrFn as T | ((prev?: NoInfer<T>) => T),
40
+ ) as Atom<T>
41
41
  }
42
-
43
- subscribe = (listener: Listener<TState>) => {
44
- this.listeners.add(listener)
45
- const unsub = this.options?.onSubscribe?.(listener, this)
46
- return () => {
47
- this.listeners.delete(listener)
48
- unsub?.()
49
- }
42
+ public get state() {
43
+ return this.atom.get()
50
44
  }
45
+ public get() {
46
+ return this.state
47
+ }
48
+ public subscribe(
49
+ observerOrFn: Observer<T> | ((value: T) => void),
50
+ ): Subscription {
51
+ return this.atom.subscribe(toObserver(observerOrFn))
52
+ }
53
+ }
51
54
 
52
- /**
53
- * Update the store state safely with improved type checking
54
- */
55
- setState(updater: (prevState: TState) => TState): void
56
- setState(updater: TState): void
57
- setState(updater: TUpdater): void
58
- setState(updater: Updater<TState> | TUpdater): void {
59
- this.prevState = this.state
60
-
61
- if (this.options?.updateFn) {
62
- this.state = this.options.updateFn(this.prevState)(updater as TUpdater)
63
- } else {
64
- if (isUpdaterFunction(updater)) {
65
- this.state = updater(this.prevState)
66
- } else {
67
- this.state = updater as TState
68
- }
69
- }
70
-
71
- // Always run onUpdate, regardless of batching
72
- this.options?.onUpdate?.()
73
-
74
- // Attempt to flush
75
- __flush(this as never)
55
+ export function createStore<T>(
56
+ getValue: (prev?: NoInfer<T>) => T,
57
+ ): ReadonlyStore<T>
58
+ export function createStore<T>(initialValue: T): Store<T>
59
+ export function createStore<T>(
60
+ valueOrFn: T | ((prev?: T) => T),
61
+ ): Store<T> | ReadonlyStore<T> {
62
+ if (typeof valueOrFn === 'function') {
63
+ return new ReadonlyStore(valueOrFn as (prev?: NoInfer<T>) => T)
76
64
  }
65
+ return new Store(valueOrFn)
77
66
  }
package/src/types.ts CHANGED
@@ -1,31 +1,67 @@
1
- /**
2
- * @private
3
- */
4
- export type AnyUpdater = (prev: any) => any
1
+ import type { ReactiveNode } from './alien'
5
2
 
6
- /**
7
- * Type-safe updater that can be either a function or direct value
8
- */
9
- export type Updater<T> = ((prev: T) => T) | T
3
+ export type Selection<TSelected> = Readable<TSelected>
10
4
 
11
- /**
12
- * @private
13
- */
14
- export interface ListenerValue<T> {
15
- readonly prevVal: T
16
- readonly currentVal: T
5
+ export interface InteropSubscribable<T> {
6
+ subscribe: (observer: Observer<T>) => Subscription
17
7
  }
18
8
 
19
- /**
20
- * @private
21
- */
22
- export type Listener<T> = (value: ListenerValue<T>) => void
9
+ // Based on RxJS types
10
+ export type Observer<T> = {
11
+ next?: (value: T) => void
12
+ error?: (err: unknown) => void
13
+ complete?: () => void
14
+ }
15
+
16
+ export interface Subscription {
17
+ unsubscribe: () => void
18
+ }
19
+
20
+ export interface Subscribable<T> extends InteropSubscribable<T> {
21
+ subscribe: ((observer: Observer<T>) => Subscription) &
22
+ ((
23
+ next: (value: T) => void,
24
+ error?: (error: any) => void,
25
+ complete?: () => void,
26
+ ) => Subscription)
27
+ }
28
+
29
+ export interface Readable<T> extends Subscribable<T> {
30
+ get: () => T
31
+ }
32
+
33
+ export interface BaseAtom<T> extends Subscribable<T>, Readable<T> {}
34
+
35
+ export interface InternalBaseAtom<T> extends Subscribable<T>, Readable<T> {
36
+ /** @internal */
37
+ _snapshot: T
38
+ /** @internal */
39
+ _update: (getValue?: T | ((snapshot: T) => T)) => boolean
40
+ }
41
+
42
+ export interface Atom<T> extends BaseAtom<T> {
43
+ /** Sets the value of the atom using a function. */
44
+ set: ((fn: (prevVal: T) => T) => void) & ((value: T) => void)
45
+ }
46
+
47
+ export interface AtomOptions<T> {
48
+ compare?: (prev: T, next: T) => boolean
49
+ }
50
+
51
+ export type AnyAtom = BaseAtom<any>
52
+
53
+ export interface InternalReadonlyAtom<T>
54
+ extends InternalBaseAtom<T>, ReactiveNode {}
23
55
 
24
56
  /**
25
- * Type guard to check if updater is a function
57
+ * An atom that is read-only and cannot be set.
58
+ *
59
+ * @example
60
+ *
61
+ * ```ts
62
+ * const atom = createAtom(() => 42);
63
+ * // @ts-expect-error - Cannot set a readonly atom
64
+ * atom.set(43);
65
+ * ```
26
66
  */
27
- export function isUpdaterFunction<T>(
28
- updater: Updater<T>,
29
- ): updater is (prev: T) => T {
30
- return typeof updater === 'function'
31
- }
67
+ export interface ReadonlyAtom<T> extends BaseAtom<T> {}
@@ -1,117 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const store = require("./store.cjs");
4
- const scheduler = require("./scheduler.cjs");
5
- class Derived {
6
- constructor(options) {
7
- this.listeners = /* @__PURE__ */ new Set();
8
- this._subscriptions = [];
9
- this.lastSeenDepValues = [];
10
- this.getDepVals = () => {
11
- const l = this.options.deps.length;
12
- const prevDepVals = new Array(l);
13
- const currDepVals = new Array(l);
14
- for (let i = 0; i < l; i++) {
15
- const dep = this.options.deps[i];
16
- prevDepVals[i] = dep.prevState;
17
- currDepVals[i] = dep.state;
18
- }
19
- this.lastSeenDepValues = currDepVals;
20
- return {
21
- prevDepVals,
22
- currDepVals,
23
- prevVal: this.prevState ?? void 0
24
- };
25
- };
26
- this.recompute = () => {
27
- var _a, _b;
28
- this.prevState = this.state;
29
- const depVals = this.getDepVals();
30
- this.state = this.options.fn(depVals);
31
- (_b = (_a = this.options).onUpdate) == null ? void 0 : _b.call(_a);
32
- };
33
- this.checkIfRecalculationNeededDeeply = () => {
34
- for (const dep of this.options.deps) {
35
- if (dep instanceof Derived) {
36
- dep.checkIfRecalculationNeededDeeply();
37
- }
38
- }
39
- let shouldRecompute = false;
40
- const lastSeenDepValues = this.lastSeenDepValues;
41
- const { currDepVals } = this.getDepVals();
42
- for (let i = 0; i < currDepVals.length; i++) {
43
- if (currDepVals[i] !== lastSeenDepValues[i]) {
44
- shouldRecompute = true;
45
- break;
46
- }
47
- }
48
- if (shouldRecompute) {
49
- this.recompute();
50
- }
51
- };
52
- this.mount = () => {
53
- this.registerOnGraph();
54
- this.checkIfRecalculationNeededDeeply();
55
- return () => {
56
- this.unregisterFromGraph();
57
- for (const cleanup of this._subscriptions) {
58
- cleanup();
59
- }
60
- };
61
- };
62
- this.subscribe = (listener) => {
63
- var _a, _b;
64
- this.listeners.add(listener);
65
- const unsub = (_b = (_a = this.options).onSubscribe) == null ? void 0 : _b.call(_a, listener, this);
66
- return () => {
67
- this.listeners.delete(listener);
68
- unsub == null ? void 0 : unsub();
69
- };
70
- };
71
- this.options = options;
72
- this.state = options.fn({
73
- prevDepVals: void 0,
74
- prevVal: void 0,
75
- currDepVals: this.getDepVals().currDepVals
76
- });
77
- }
78
- registerOnGraph(deps = this.options.deps) {
79
- for (const dep of deps) {
80
- if (dep instanceof Derived) {
81
- dep.registerOnGraph();
82
- this.registerOnGraph(dep.options.deps);
83
- } else if (dep instanceof store.Store) {
84
- let relatedLinkedDerivedVals = scheduler.__storeToDerived.get(dep);
85
- if (!relatedLinkedDerivedVals) {
86
- relatedLinkedDerivedVals = /* @__PURE__ */ new Set();
87
- scheduler.__storeToDerived.set(dep, relatedLinkedDerivedVals);
88
- }
89
- relatedLinkedDerivedVals.add(this);
90
- let relatedStores = scheduler.__derivedToStore.get(this);
91
- if (!relatedStores) {
92
- relatedStores = /* @__PURE__ */ new Set();
93
- scheduler.__derivedToStore.set(this, relatedStores);
94
- }
95
- relatedStores.add(dep);
96
- }
97
- }
98
- }
99
- unregisterFromGraph(deps = this.options.deps) {
100
- for (const dep of deps) {
101
- if (dep instanceof Derived) {
102
- this.unregisterFromGraph(dep.options.deps);
103
- } else if (dep instanceof store.Store) {
104
- const relatedLinkedDerivedVals = scheduler.__storeToDerived.get(dep);
105
- if (relatedLinkedDerivedVals) {
106
- relatedLinkedDerivedVals.delete(this);
107
- }
108
- const relatedStores = scheduler.__derivedToStore.get(this);
109
- if (relatedStores) {
110
- relatedStores.delete(dep);
111
- }
112
- }
113
- }
114
- }
115
- }
116
- exports.Derived = Derived;
117
- //# sourceMappingURL=derived.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"derived.cjs","sources":["../../src/derived.ts"],"sourcesContent":["import { Store } from './store'\nimport { __derivedToStore, __storeToDerived } from './scheduler'\nimport type { Listener } from './types'\n\nexport type UnwrapDerivedOrStore<T> =\n T extends Derived<infer InnerD>\n ? InnerD\n : T extends Store<infer InnerS>\n ? InnerS\n : never\n\ntype UnwrapReadonlyDerivedOrStoreArray<\n TArr extends ReadonlyArray<Derived<any> | Store<any>>,\n> = TArr extends readonly []\n ? []\n : TArr extends readonly [infer Head, ...infer Tail]\n ? Head extends Derived<any> | Store<any>\n ? Tail extends ReadonlyArray<Derived<any> | Store<any>>\n ? [\n UnwrapDerivedOrStore<Head>,\n ...UnwrapReadonlyDerivedOrStoreArray<Tail>,\n ]\n : [UnwrapDerivedOrStore<Head>]\n : []\n : TArr extends ReadonlyArray<Derived<any> | Store<any>>\n ? Array<UnwrapDerivedOrStore<TArr[number]>>\n : []\n\n// Can't have currVal, as it's being evaluated from the current derived fn\nexport interface DerivedFnProps<\n TArr extends ReadonlyArray<Derived<any> | Store<any>> = ReadonlyArray<any>,\n TUnwrappedArr extends\n UnwrapReadonlyDerivedOrStoreArray<TArr> = UnwrapReadonlyDerivedOrStoreArray<TArr>,\n> {\n // `undefined` if it's the first run\n /**\n * `undefined` if it's the first run\n * @privateRemarks this also cannot be typed as TState, as it breaks the inferencing of the function's return type when an argument is used - even with `NoInfer` usage\n */\n prevVal: unknown | undefined\n prevDepVals: TUnwrappedArr | undefined\n currDepVals: TUnwrappedArr\n}\n\nexport interface DerivedOptions<\n TState,\n TArr extends ReadonlyArray<Derived<any> | Store<any>> = ReadonlyArray<any>,\n> {\n onSubscribe?: (\n listener: Listener<TState>,\n derived: Derived<TState>,\n ) => () => void\n onUpdate?: () => void\n deps: TArr\n /**\n * Values of the `deps` from before and after the current invocation of `fn`\n */\n fn: (props: DerivedFnProps<TArr>) => TState\n}\n\nexport class Derived<\n TState,\n const TArr extends ReadonlyArray<\n Derived<any> | Store<any>\n > = ReadonlyArray<any>,\n> {\n listeners = new Set<Listener<TState>>()\n state: TState\n prevState: TState | undefined\n options: DerivedOptions<TState, TArr>\n\n /**\n * Functions representing the subscriptions. Call a function to cleanup\n * @private\n */\n _subscriptions: Array<() => void> = []\n\n lastSeenDepValues: Array<unknown> = []\n getDepVals = () => {\n const l = this.options.deps.length\n const prevDepVals = new Array<unknown>(l)\n const currDepVals = new Array<unknown>(l)\n for (let i = 0; i < l; i++) {\n const dep = this.options.deps[i]!\n prevDepVals[i] = dep.prevState\n currDepVals[i] = dep.state\n }\n this.lastSeenDepValues = currDepVals\n return {\n prevDepVals,\n currDepVals,\n prevVal: this.prevState ?? undefined,\n }\n }\n\n constructor(options: DerivedOptions<TState, TArr>) {\n this.options = options\n this.state = options.fn({\n prevDepVals: undefined,\n prevVal: undefined,\n currDepVals: this.getDepVals().currDepVals as never,\n })\n }\n\n registerOnGraph(\n deps: ReadonlyArray<Derived<any> | Store<any>> = this.options.deps,\n ) {\n for (const dep of deps) {\n if (dep instanceof Derived) {\n // First register the intermediate derived value if it's not already registered\n dep.registerOnGraph()\n // Then register this derived with the dep's underlying stores\n this.registerOnGraph(dep.options.deps)\n } else if (dep instanceof Store) {\n // Register the derived as related derived to the store\n let relatedLinkedDerivedVals = __storeToDerived.get(dep)\n if (!relatedLinkedDerivedVals) {\n relatedLinkedDerivedVals = new Set()\n __storeToDerived.set(dep, relatedLinkedDerivedVals)\n }\n relatedLinkedDerivedVals.add(this as never)\n\n // Register the store as a related store to this derived\n let relatedStores = __derivedToStore.get(this as never)\n if (!relatedStores) {\n relatedStores = new Set()\n __derivedToStore.set(this as never, relatedStores)\n }\n relatedStores.add(dep)\n }\n }\n }\n\n unregisterFromGraph(\n deps: ReadonlyArray<Derived<any> | Store<any>> = this.options.deps,\n ) {\n for (const dep of deps) {\n if (dep instanceof Derived) {\n this.unregisterFromGraph(dep.options.deps)\n } else if (dep instanceof Store) {\n const relatedLinkedDerivedVals = __storeToDerived.get(dep)\n if (relatedLinkedDerivedVals) {\n relatedLinkedDerivedVals.delete(this as never)\n }\n\n const relatedStores = __derivedToStore.get(this as never)\n if (relatedStores) {\n relatedStores.delete(dep)\n }\n }\n }\n }\n\n recompute = () => {\n this.prevState = this.state\n const depVals = this.getDepVals()\n this.state = this.options.fn(depVals as never)\n\n this.options.onUpdate?.()\n }\n\n checkIfRecalculationNeededDeeply = () => {\n for (const dep of this.options.deps) {\n if (dep instanceof Derived) {\n dep.checkIfRecalculationNeededDeeply()\n }\n }\n let shouldRecompute = false\n const lastSeenDepValues = this.lastSeenDepValues\n const { currDepVals } = this.getDepVals()\n for (let i = 0; i < currDepVals.length; i++) {\n if (currDepVals[i] !== lastSeenDepValues[i]) {\n shouldRecompute = true\n break\n }\n }\n\n if (shouldRecompute) {\n this.recompute()\n }\n }\n\n mount = () => {\n this.registerOnGraph()\n this.checkIfRecalculationNeededDeeply()\n\n return () => {\n this.unregisterFromGraph()\n for (const cleanup of this._subscriptions) {\n cleanup()\n }\n }\n }\n\n subscribe = (listener: Listener<TState>) => {\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"],"names":["Store","__storeToDerived","__derivedToStore"],"mappings":"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"}