watch-state 3.5.0-alpha.3 → 3.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/Cache/Cache.d.ts +5 -15
- package/Cache/Cache.es6.js +6 -68
- package/Cache/Cache.js +6 -68
- package/Cache/Cache.test.d.ts +1 -0
- package/Compute/Compute.d.ts +82 -0
- package/Compute/Compute.es6.js +184 -0
- package/Compute/Compute.js +191 -0
- package/Compute/Compute.test.d.ts +1 -0
- package/Compute/index.d.ts +1 -0
- package/Compute/index.es6.js +1 -0
- package/Compute/index.js +12 -0
- package/Observable/Observable.d.ts +21 -1
- package/Observable/Observable.es6.js +17 -0
- package/Observable/Observable.js +17 -0
- package/README.md +421 -111
- package/State/State.d.ts +45 -1
- package/State/State.es6.js +48 -4
- package/State/State.js +49 -5
- package/State/State.test.d.ts +1 -0
- package/Watch/Watch.d.ts +26 -5
- package/Watch/Watch.es6.js +30 -13
- package/Watch/Watch.js +30 -13
- package/Watch/Watch.test.d.ts +1 -0
- package/constants.d.ts +1 -0
- package/constants.es6.js +1 -0
- package/constants.js +1 -0
- package/helpers/bindObserver/bindObserver.d.ts +2 -0
- package/helpers/bindObserver/bindObserver.es6.js +13 -0
- package/helpers/bindObserver/bindObserver.js +17 -0
- package/helpers/bindObserver/index.d.ts +1 -0
- package/helpers/bindObserver/index.es6.js +1 -0
- package/helpers/bindObserver/index.js +9 -0
- package/helpers/{clearWatchers → clearWatcher}/clearWatcher.es6.js +1 -1
- package/helpers/{clearWatchers → clearWatcher}/clearWatcher.js +1 -1
- package/helpers/destroyWatchers/destroyWatchers.es6.js +1 -1
- package/helpers/destroyWatchers/destroyWatchers.js +1 -1
- package/helpers/index.d.ts +3 -3
- package/helpers/index.es6.js +3 -3
- package/helpers/index.js +3 -3
- package/helpers/invalidateCache/invalidateCache.d.ts +4 -1
- package/helpers/invalidateCache/invalidateCache.es6.js +8 -13
- package/helpers/invalidateCache/invalidateCache.js +8 -13
- package/helpers/watchWithScope/watchWithScope.d.ts +1 -1
- package/helpers/watchWithScope/watchWithScope.es6.js +4 -4
- package/helpers/watchWithScope/watchWithScope.js +4 -4
- package/index.d.ts +6 -5
- package/index.es6.js +17 -14
- package/index.js +31 -24
- package/index.min.js +1 -0
- package/index.test.d.ts +1 -0
- package/package.json +30 -7
- package/types.d.ts +24 -3
- package/utils/callEvent/callEvent.d.ts +50 -0
- package/utils/callEvent/callEvent.es6.js +69 -0
- package/utils/callEvent/callEvent.js +73 -0
- package/utils/callEvent/callEvent.test.d.ts +1 -0
- package/utils/callEvent/index.d.ts +1 -0
- package/utils/callEvent/index.es6.js +1 -0
- package/utils/callEvent/index.js +9 -0
- package/utils/createEvent/createEvent.d.ts +52 -6
- package/utils/createEvent/createEvent.es6.js +57 -11
- package/utils/createEvent/createEvent.js +58 -12
- package/utils/createEvent/createEvent.test.d.ts +1 -0
- package/utils/index.d.ts +3 -2
- package/utils/index.es6.js +3 -2
- package/utils/index.js +3 -2
- package/utils/onDestroy/onDestroy.d.ts +3 -2
- package/utils/onDestroy/onDestroy.es6.js +1 -1
- package/utils/onDestroy/onDestroy.js +1 -1
- package/utils/onDestroy/onDestroy.test.d.ts +1 -0
- package/utils/unwatch/unwatch.d.ts +10 -5
- package/utils/unwatch/unwatch.es6.js +10 -5
- package/utils/unwatch/unwatch.js +10 -5
- package/utils/unwatch/unwatch.test.d.ts +1 -0
- package/helpers/queueWatchers/index.d.ts +0 -1
- package/helpers/queueWatchers/index.es6.js +0 -1
- package/helpers/queueWatchers/index.js +0 -10
- package/helpers/queueWatchers/queueWatchers.d.ts +0 -3
- package/helpers/queueWatchers/queueWatchers.es6.js +0 -42
- package/helpers/queueWatchers/queueWatchers.js +0 -47
- /package/helpers/{clearWatchers → clearWatcher}/clearWatcher.d.ts +0 -0
- /package/helpers/{clearWatchers → clearWatcher}/index.d.ts +0 -0
- /package/helpers/{clearWatchers → clearWatcher}/index.es6.js +0 -0
- /package/helpers/{clearWatchers → clearWatcher}/index.js +0 -0
package/Cache/Cache.d.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
destroyed: boolean;
|
|
7
|
-
isCache: boolean;
|
|
8
|
-
destructors: Set<Function>;
|
|
9
|
-
childWatchers: Set<Observer>;
|
|
10
|
-
readonly watcher: Watcher<V>;
|
|
11
|
-
constructor(watcher: Watcher<V>, freeParent?: boolean, fireImmediately?: boolean);
|
|
12
|
-
update(): void;
|
|
13
|
-
forceUpdate(): void;
|
|
14
|
-
get value(): V;
|
|
15
|
-
destroy(): void;
|
|
1
|
+
import { Compute } from '../Compute';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Use `Compute`
|
|
4
|
+
*/
|
|
5
|
+
export declare class Cache<V = unknown> extends Compute<V> {
|
|
16
6
|
}
|
package/Cache/Cache.es6.js
CHANGED
|
@@ -1,72 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import '../
|
|
3
|
-
import '../Observable/index.es6.js';
|
|
4
|
-
import '../Watch/index.es6.js';
|
|
5
|
-
import { Observable } from '../Observable/Observable.es6.js';
|
|
6
|
-
import { invalidateCache } from '../helpers/invalidateCache/invalidateCache.es6.js';
|
|
7
|
-
import { Watch } from '../Watch/Watch.es6.js';
|
|
8
|
-
import { watchWithScope } from '../helpers/watchWithScope/watchWithScope.es6.js';
|
|
9
|
-
import { queueWatchers } from '../helpers/queueWatchers/queueWatchers.es6.js';
|
|
10
|
-
import { destroyWatchers } from '../helpers/destroyWatchers/destroyWatchers.es6.js';
|
|
1
|
+
import '../Compute/index.es6.js';
|
|
2
|
+
import { Compute } from '../Compute/Compute.es6.js';
|
|
11
3
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.updated = false;
|
|
17
|
-
this.destroyed = false;
|
|
18
|
-
this.isCache = true;
|
|
19
|
-
// Observer
|
|
20
|
-
this.destructors = new Set();
|
|
21
|
-
this.childWatchers = new Set();
|
|
22
|
-
this.watcher = watcher;
|
|
23
|
-
if (!freeParent) {
|
|
24
|
-
const { activeWatcher } = scope;
|
|
25
|
-
if (activeWatcher) {
|
|
26
|
-
activeWatcher.childWatchers.add(this);
|
|
27
|
-
activeWatcher.destructors.add(() => {
|
|
28
|
-
activeWatcher.childWatchers.delete(this);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (fireImmediately) {
|
|
33
|
-
this.forceUpdate();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
update() {
|
|
37
|
-
invalidateCache(this);
|
|
38
|
-
const parents = [...this.observers];
|
|
39
|
-
let parent;
|
|
40
|
-
while ((parent = parents.pop())) {
|
|
41
|
-
if (parent instanceof Watch) {
|
|
42
|
-
return this.forceUpdate();
|
|
43
|
-
}
|
|
44
|
-
if (parent instanceof Cache) {
|
|
45
|
-
parents.push(...parent.observers);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
forceUpdate() {
|
|
50
|
-
if (!this.destroyed) {
|
|
51
|
-
this.invalid = false;
|
|
52
|
-
watchWithScope(this, () => {
|
|
53
|
-
const newValue = this.watcher(this.updated ? this.updated = true : false);
|
|
54
|
-
if (newValue !== this.rawValue) {
|
|
55
|
-
this.rawValue = newValue;
|
|
56
|
-
queueWatchers(this.observers);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
get value() {
|
|
62
|
-
if (this.invalid) {
|
|
63
|
-
this.forceUpdate();
|
|
64
|
-
}
|
|
65
|
-
return this.destroyed ? this.rawValue : super.value;
|
|
66
|
-
}
|
|
67
|
-
destroy() {
|
|
68
|
-
destroyWatchers(this);
|
|
69
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Use `Compute`
|
|
6
|
+
*/
|
|
7
|
+
class Cache extends Compute {
|
|
70
8
|
}
|
|
71
9
|
|
|
72
10
|
export { Cache };
|
package/Cache/Cache.js
CHANGED
|
@@ -2,75 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
require('../
|
|
7
|
-
require('../Observable/index.js');
|
|
8
|
-
require('../Watch/index.js');
|
|
9
|
-
var Observable = require('../Observable/Observable.js');
|
|
10
|
-
var invalidateCache = require('../helpers/invalidateCache/invalidateCache.js');
|
|
11
|
-
var Watch = require('../Watch/Watch.js');
|
|
12
|
-
var watchWithScope = require('../helpers/watchWithScope/watchWithScope.js');
|
|
13
|
-
var queueWatchers = require('../helpers/queueWatchers/queueWatchers.js');
|
|
14
|
-
var destroyWatchers = require('../helpers/destroyWatchers/destroyWatchers.js');
|
|
5
|
+
require('../Compute/index.js');
|
|
6
|
+
var Compute = require('../Compute/Compute.js');
|
|
15
7
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.updated = false;
|
|
21
|
-
this.destroyed = false;
|
|
22
|
-
this.isCache = true;
|
|
23
|
-
// Observer
|
|
24
|
-
this.destructors = new Set();
|
|
25
|
-
this.childWatchers = new Set();
|
|
26
|
-
this.watcher = watcher;
|
|
27
|
-
if (!freeParent) {
|
|
28
|
-
const { activeWatcher } = constants.scope;
|
|
29
|
-
if (activeWatcher) {
|
|
30
|
-
activeWatcher.childWatchers.add(this);
|
|
31
|
-
activeWatcher.destructors.add(() => {
|
|
32
|
-
activeWatcher.childWatchers.delete(this);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (fireImmediately) {
|
|
37
|
-
this.forceUpdate();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
update() {
|
|
41
|
-
invalidateCache.invalidateCache(this);
|
|
42
|
-
const parents = [...this.observers];
|
|
43
|
-
let parent;
|
|
44
|
-
while ((parent = parents.pop())) {
|
|
45
|
-
if (parent instanceof Watch.Watch) {
|
|
46
|
-
return this.forceUpdate();
|
|
47
|
-
}
|
|
48
|
-
if (parent instanceof Cache) {
|
|
49
|
-
parents.push(...parent.observers);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
forceUpdate() {
|
|
54
|
-
if (!this.destroyed) {
|
|
55
|
-
this.invalid = false;
|
|
56
|
-
watchWithScope.watchWithScope(this, () => {
|
|
57
|
-
const newValue = this.watcher(this.updated ? this.updated = true : false);
|
|
58
|
-
if (newValue !== this.rawValue) {
|
|
59
|
-
this.rawValue = newValue;
|
|
60
|
-
queueWatchers.queueWatchers(this.observers);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
get value() {
|
|
66
|
-
if (this.invalid) {
|
|
67
|
-
this.forceUpdate();
|
|
68
|
-
}
|
|
69
|
-
return this.destroyed ? this.rawValue : super.value;
|
|
70
|
-
}
|
|
71
|
-
destroy() {
|
|
72
|
-
destroyWatchers.destroyWatchers(this);
|
|
73
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use `Compute`
|
|
10
|
+
*/
|
|
11
|
+
class Cache extends Compute.Compute {
|
|
74
12
|
}
|
|
75
13
|
|
|
76
14
|
exports.Cache = Cache;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Observable } from '../Observable';
|
|
2
|
+
import type { Destructor, Observer, Watcher } from '../types';
|
|
3
|
+
export declare function forceQueueWatchers(): void;
|
|
4
|
+
export declare function queueWatchers(observers: Set<Observer>): void;
|
|
5
|
+
export declare function invalidateCompute(observer: Observer): void;
|
|
6
|
+
/**
|
|
7
|
+
* Cached reactive computation with memoization.
|
|
8
|
+
* Recalculates value only when dependencies change and when it is actively consumed
|
|
9
|
+
* by a Watcher or another Compute that is itself consumed by a Watcher.
|
|
10
|
+
*
|
|
11
|
+
* This ensures that computations are only evaluated when their output is actually needed,
|
|
12
|
+
* enabling efficient lazy evaluation and automatic subscription management.
|
|
13
|
+
*
|
|
14
|
+
* @class Compute
|
|
15
|
+
* @extends Observable<V>
|
|
16
|
+
* @implements {Observer}
|
|
17
|
+
* @template V - computed value type
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const fullName = new State('Mighty Mike')
|
|
21
|
+
* const name = new Compute(() => fullName.value.split(' ')[1])
|
|
22
|
+
*
|
|
23
|
+
* // Only when accessed inside an `Observer`, `Compute` becomes active:
|
|
24
|
+
*
|
|
25
|
+
* const nameWatcher = new Watch(() => console.log(name.value))
|
|
26
|
+
* // Triggers computation and subscribes to `name`
|
|
27
|
+
*
|
|
28
|
+
* // This does NOT trigger recomputation:
|
|
29
|
+
* console.log(name.value)
|
|
30
|
+
*
|
|
31
|
+
* // If used inside another `Compute` that is watched, it triggers:
|
|
32
|
+
* const greeting = new Compute(() => `${name.value} How are you?`)
|
|
33
|
+
*
|
|
34
|
+
* const greetingWatcher new Watch(() => console.log(greeting.value))
|
|
35
|
+
* // Triggers greeting
|
|
36
|
+
*
|
|
37
|
+
* fullName.value = 'Mighty Michael'
|
|
38
|
+
* // Triggers full chain: fullName → name → greeting → greetingWatcher
|
|
39
|
+
*
|
|
40
|
+
* fullName.value = 'Deight Michael'
|
|
41
|
+
* // Triggers part of chain: fullName → name
|
|
42
|
+
*/
|
|
43
|
+
export declare class Compute<V = unknown> extends Observable<V> implements Observer {
|
|
44
|
+
/** Indicates if computed value is stale and needs recalculation. */
|
|
45
|
+
invalid: boolean;
|
|
46
|
+
/** Tracks if the computation has run at least once. */
|
|
47
|
+
updated: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Indicates if observer has been destroyed.
|
|
50
|
+
* Prevents accidental use after cleanup.
|
|
51
|
+
*/
|
|
52
|
+
destroyed: boolean;
|
|
53
|
+
/** @deprecated Use `observer instanceof Compute` */
|
|
54
|
+
isCache: boolean;
|
|
55
|
+
/** Cleanup functions to run on destroy (e.g., unsubscribes). */
|
|
56
|
+
readonly destructors: Set<Destructor>;
|
|
57
|
+
/** Child watchers created within this watcher's scope */
|
|
58
|
+
readonly childrenObservers: Set<Observer>;
|
|
59
|
+
/** @deprecated Use `childrenObservers` */
|
|
60
|
+
get childWatchers(): Set<Observer>;
|
|
61
|
+
readonly watcher: Watcher<V>;
|
|
62
|
+
constructor(watcher: Watcher<V>, freeParent?: boolean, fireImmediately?: boolean);
|
|
63
|
+
/** Mark computation as invalid and trigger propagation to parent observers. */
|
|
64
|
+
update(): void;
|
|
65
|
+
forceUpdate(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Current value with automatic subscription.
|
|
68
|
+
*
|
|
69
|
+
* Accessing `value` inside an `Observer` automatically subscribes the watcher.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const count = new State(0)
|
|
73
|
+
* const text = new Compute(() => `Count: ${count.value}`)
|
|
74
|
+
*
|
|
75
|
+
* new Watch(() => console.log(text.value)) // Count: 0
|
|
76
|
+
*
|
|
77
|
+
* count.value++ // Count: 1
|
|
78
|
+
*/
|
|
79
|
+
get value(): V;
|
|
80
|
+
/** Stop observation and remove all dependencies. */
|
|
81
|
+
destroy(): void;
|
|
82
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { scope } from '../constants.es6.js';
|
|
2
|
+
import '../helpers/bindObserver/index.es6.js';
|
|
3
|
+
import '../helpers/clearWatcher/index.es6.js';
|
|
4
|
+
import '../helpers/destroyWatchers/index.es6.js';
|
|
5
|
+
import '../helpers/watchWithScope/index.es6.js';
|
|
6
|
+
import '../Observable/index.es6.js';
|
|
7
|
+
import '../utils/shiftSet/index.es6.js';
|
|
8
|
+
import { shiftSet } from '../utils/shiftSet/shiftSet.es6.js';
|
|
9
|
+
import { clearWatcher } from '../helpers/clearWatcher/clearWatcher.es6.js';
|
|
10
|
+
import { Observable } from '../Observable/Observable.es6.js';
|
|
11
|
+
import { bindObserver } from '../helpers/bindObserver/bindObserver.es6.js';
|
|
12
|
+
import { watchWithScope } from '../helpers/watchWithScope/watchWithScope.es6.js';
|
|
13
|
+
import { destroyWatchers } from '../helpers/destroyWatchers/destroyWatchers.es6.js';
|
|
14
|
+
|
|
15
|
+
/* queue */
|
|
16
|
+
let currentCompute;
|
|
17
|
+
let currentObserver;
|
|
18
|
+
let forcedQueue;
|
|
19
|
+
const computeStack = new Set();
|
|
20
|
+
const observersStack = new Set();
|
|
21
|
+
function forceQueueWatchers() {
|
|
22
|
+
if (forcedQueue)
|
|
23
|
+
return;
|
|
24
|
+
forcedQueue = true;
|
|
25
|
+
while ((currentCompute = shiftSet(computeStack)) || (currentObserver = shiftSet(observersStack))) {
|
|
26
|
+
if (currentCompute) {
|
|
27
|
+
currentCompute.invalid = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
clearWatcher(currentObserver);
|
|
31
|
+
currentObserver.update();
|
|
32
|
+
}
|
|
33
|
+
forcedQueue = false;
|
|
34
|
+
}
|
|
35
|
+
function queueWatchers(observers) {
|
|
36
|
+
const useLoop = !scope.eventDeep && !observersStack.size && !computeStack.size;
|
|
37
|
+
const oldObserversStack = [...observersStack];
|
|
38
|
+
observersStack.clear();
|
|
39
|
+
observers.forEach(watcher => {
|
|
40
|
+
observersStack.add(watcher);
|
|
41
|
+
if (watcher instanceof Compute) {
|
|
42
|
+
computeStack.add(watcher);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
oldObserversStack.forEach(observer => observersStack.add(observer));
|
|
46
|
+
if (useLoop) {
|
|
47
|
+
forceQueueWatchers();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/* invalidateCompute */
|
|
51
|
+
const invalidateStack = [];
|
|
52
|
+
let currentInvalidateObserver;
|
|
53
|
+
function invalidateCompute(observer) {
|
|
54
|
+
const skipLoop = invalidateStack.length;
|
|
55
|
+
invalidateStack.push(observer);
|
|
56
|
+
if (skipLoop)
|
|
57
|
+
return;
|
|
58
|
+
while ((currentInvalidateObserver = invalidateStack.shift())) {
|
|
59
|
+
if (currentInvalidateObserver instanceof Compute) {
|
|
60
|
+
invalidateStack.push(...currentInvalidateObserver.observers);
|
|
61
|
+
currentInvalidateObserver.invalid = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/* Compute */
|
|
66
|
+
/**
|
|
67
|
+
* Cached reactive computation with memoization.
|
|
68
|
+
* Recalculates value only when dependencies change and when it is actively consumed
|
|
69
|
+
* by a Watcher or another Compute that is itself consumed by a Watcher.
|
|
70
|
+
*
|
|
71
|
+
* This ensures that computations are only evaluated when their output is actually needed,
|
|
72
|
+
* enabling efficient lazy evaluation and automatic subscription management.
|
|
73
|
+
*
|
|
74
|
+
* @class Compute
|
|
75
|
+
* @extends Observable<V>
|
|
76
|
+
* @implements {Observer}
|
|
77
|
+
* @template V - computed value type
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const fullName = new State('Mighty Mike')
|
|
81
|
+
* const name = new Compute(() => fullName.value.split(' ')[1])
|
|
82
|
+
*
|
|
83
|
+
* // Only when accessed inside an `Observer`, `Compute` becomes active:
|
|
84
|
+
*
|
|
85
|
+
* const nameWatcher = new Watch(() => console.log(name.value))
|
|
86
|
+
* // Triggers computation and subscribes to `name`
|
|
87
|
+
*
|
|
88
|
+
* // This does NOT trigger recomputation:
|
|
89
|
+
* console.log(name.value)
|
|
90
|
+
*
|
|
91
|
+
* // If used inside another `Compute` that is watched, it triggers:
|
|
92
|
+
* const greeting = new Compute(() => `${name.value} How are you?`)
|
|
93
|
+
*
|
|
94
|
+
* const greetingWatcher new Watch(() => console.log(greeting.value))
|
|
95
|
+
* // Triggers greeting
|
|
96
|
+
*
|
|
97
|
+
* fullName.value = 'Mighty Michael'
|
|
98
|
+
* // Triggers full chain: fullName → name → greeting → greetingWatcher
|
|
99
|
+
*
|
|
100
|
+
* fullName.value = 'Deight Michael'
|
|
101
|
+
* // Triggers part of chain: fullName → name
|
|
102
|
+
*/
|
|
103
|
+
class Compute extends Observable {
|
|
104
|
+
// TODO: remove in major release
|
|
105
|
+
/** @deprecated Use `childrenObservers` */
|
|
106
|
+
get childWatchers() {
|
|
107
|
+
return this.childrenObservers;
|
|
108
|
+
}
|
|
109
|
+
constructor(watcher, freeParent, fireImmediately) {
|
|
110
|
+
super();
|
|
111
|
+
/** Indicates if computed value is stale and needs recalculation. */
|
|
112
|
+
this.invalid = true;
|
|
113
|
+
/** Tracks if the computation has run at least once. */
|
|
114
|
+
this.updated = false;
|
|
115
|
+
/**
|
|
116
|
+
* Indicates if observer has been destroyed.
|
|
117
|
+
* Prevents accidental use after cleanup.
|
|
118
|
+
*/
|
|
119
|
+
this.destroyed = false;
|
|
120
|
+
// TODO: remove in major release
|
|
121
|
+
/** @deprecated Use `observer instanceof Compute` */
|
|
122
|
+
this.isCache = true;
|
|
123
|
+
/** Cleanup functions to run on destroy (e.g., unsubscribes). */
|
|
124
|
+
this.destructors = new Set();
|
|
125
|
+
/** Child watchers created within this watcher's scope */
|
|
126
|
+
this.childrenObservers = new Set();
|
|
127
|
+
this.watcher = watcher;
|
|
128
|
+
if (!freeParent) {
|
|
129
|
+
bindObserver(this);
|
|
130
|
+
}
|
|
131
|
+
if (fireImmediately) {
|
|
132
|
+
this.forceUpdate();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Mark computation as invalid and trigger propagation to parent observers. */
|
|
136
|
+
update() {
|
|
137
|
+
invalidateCompute(this);
|
|
138
|
+
const parents = [...this.observers];
|
|
139
|
+
let parent;
|
|
140
|
+
while ((parent = parents.pop())) {
|
|
141
|
+
if (!(parent instanceof Compute)) {
|
|
142
|
+
return this.forceUpdate();
|
|
143
|
+
}
|
|
144
|
+
parents.push(...parent.observers);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
forceUpdate() {
|
|
148
|
+
if (!this.destroyed) {
|
|
149
|
+
this.invalid = false;
|
|
150
|
+
watchWithScope(this, () => {
|
|
151
|
+
const newValue = this.watcher(this.updated ? this.updated = true : false);
|
|
152
|
+
if (newValue !== this.rawValue) {
|
|
153
|
+
this.rawValue = newValue;
|
|
154
|
+
queueWatchers(this.observers);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Current value with automatic subscription.
|
|
161
|
+
*
|
|
162
|
+
* Accessing `value` inside an `Observer` automatically subscribes the watcher.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* const count = new State(0)
|
|
166
|
+
* const text = new Compute(() => `Count: ${count.value}`)
|
|
167
|
+
*
|
|
168
|
+
* new Watch(() => console.log(text.value)) // Count: 0
|
|
169
|
+
*
|
|
170
|
+
* count.value++ // Count: 1
|
|
171
|
+
*/
|
|
172
|
+
get value() {
|
|
173
|
+
if (this.invalid) {
|
|
174
|
+
this.forceUpdate();
|
|
175
|
+
}
|
|
176
|
+
return this.destroyed ? this.rawValue : super.value;
|
|
177
|
+
}
|
|
178
|
+
/** Stop observation and remove all dependencies. */
|
|
179
|
+
destroy() {
|
|
180
|
+
destroyWatchers(this);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { Compute, forceQueueWatchers, invalidateCompute, queueWatchers };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var constants = require('../constants.js');
|
|
6
|
+
require('../helpers/bindObserver/index.js');
|
|
7
|
+
require('../helpers/clearWatcher/index.js');
|
|
8
|
+
require('../helpers/destroyWatchers/index.js');
|
|
9
|
+
require('../helpers/watchWithScope/index.js');
|
|
10
|
+
require('../Observable/index.js');
|
|
11
|
+
require('../utils/shiftSet/index.js');
|
|
12
|
+
var shiftSet = require('../utils/shiftSet/shiftSet.js');
|
|
13
|
+
var clearWatcher = require('../helpers/clearWatcher/clearWatcher.js');
|
|
14
|
+
var Observable = require('../Observable/Observable.js');
|
|
15
|
+
var bindObserver = require('../helpers/bindObserver/bindObserver.js');
|
|
16
|
+
var watchWithScope = require('../helpers/watchWithScope/watchWithScope.js');
|
|
17
|
+
var destroyWatchers = require('../helpers/destroyWatchers/destroyWatchers.js');
|
|
18
|
+
|
|
19
|
+
/* queue */
|
|
20
|
+
let currentCompute;
|
|
21
|
+
let currentObserver;
|
|
22
|
+
let forcedQueue;
|
|
23
|
+
const computeStack = new Set();
|
|
24
|
+
const observersStack = new Set();
|
|
25
|
+
function forceQueueWatchers() {
|
|
26
|
+
if (forcedQueue)
|
|
27
|
+
return;
|
|
28
|
+
forcedQueue = true;
|
|
29
|
+
while ((currentCompute = shiftSet.shiftSet(computeStack)) || (currentObserver = shiftSet.shiftSet(observersStack))) {
|
|
30
|
+
if (currentCompute) {
|
|
31
|
+
currentCompute.invalid = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
clearWatcher.clearWatcher(currentObserver);
|
|
35
|
+
currentObserver.update();
|
|
36
|
+
}
|
|
37
|
+
forcedQueue = false;
|
|
38
|
+
}
|
|
39
|
+
function queueWatchers(observers) {
|
|
40
|
+
const useLoop = !constants.scope.eventDeep && !observersStack.size && !computeStack.size;
|
|
41
|
+
const oldObserversStack = [...observersStack];
|
|
42
|
+
observersStack.clear();
|
|
43
|
+
observers.forEach(watcher => {
|
|
44
|
+
observersStack.add(watcher);
|
|
45
|
+
if (watcher instanceof Compute) {
|
|
46
|
+
computeStack.add(watcher);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
oldObserversStack.forEach(observer => observersStack.add(observer));
|
|
50
|
+
if (useLoop) {
|
|
51
|
+
forceQueueWatchers();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/* invalidateCompute */
|
|
55
|
+
const invalidateStack = [];
|
|
56
|
+
let currentInvalidateObserver;
|
|
57
|
+
function invalidateCompute(observer) {
|
|
58
|
+
const skipLoop = invalidateStack.length;
|
|
59
|
+
invalidateStack.push(observer);
|
|
60
|
+
if (skipLoop)
|
|
61
|
+
return;
|
|
62
|
+
while ((currentInvalidateObserver = invalidateStack.shift())) {
|
|
63
|
+
if (currentInvalidateObserver instanceof Compute) {
|
|
64
|
+
invalidateStack.push(...currentInvalidateObserver.observers);
|
|
65
|
+
currentInvalidateObserver.invalid = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/* Compute */
|
|
70
|
+
/**
|
|
71
|
+
* Cached reactive computation with memoization.
|
|
72
|
+
* Recalculates value only when dependencies change and when it is actively consumed
|
|
73
|
+
* by a Watcher or another Compute that is itself consumed by a Watcher.
|
|
74
|
+
*
|
|
75
|
+
* This ensures that computations are only evaluated when their output is actually needed,
|
|
76
|
+
* enabling efficient lazy evaluation and automatic subscription management.
|
|
77
|
+
*
|
|
78
|
+
* @class Compute
|
|
79
|
+
* @extends Observable<V>
|
|
80
|
+
* @implements {Observer}
|
|
81
|
+
* @template V - computed value type
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const fullName = new State('Mighty Mike')
|
|
85
|
+
* const name = new Compute(() => fullName.value.split(' ')[1])
|
|
86
|
+
*
|
|
87
|
+
* // Only when accessed inside an `Observer`, `Compute` becomes active:
|
|
88
|
+
*
|
|
89
|
+
* const nameWatcher = new Watch(() => console.log(name.value))
|
|
90
|
+
* // Triggers computation and subscribes to `name`
|
|
91
|
+
*
|
|
92
|
+
* // This does NOT trigger recomputation:
|
|
93
|
+
* console.log(name.value)
|
|
94
|
+
*
|
|
95
|
+
* // If used inside another `Compute` that is watched, it triggers:
|
|
96
|
+
* const greeting = new Compute(() => `${name.value} How are you?`)
|
|
97
|
+
*
|
|
98
|
+
* const greetingWatcher new Watch(() => console.log(greeting.value))
|
|
99
|
+
* // Triggers greeting
|
|
100
|
+
*
|
|
101
|
+
* fullName.value = 'Mighty Michael'
|
|
102
|
+
* // Triggers full chain: fullName → name → greeting → greetingWatcher
|
|
103
|
+
*
|
|
104
|
+
* fullName.value = 'Deight Michael'
|
|
105
|
+
* // Triggers part of chain: fullName → name
|
|
106
|
+
*/
|
|
107
|
+
class Compute extends Observable.Observable {
|
|
108
|
+
// TODO: remove in major release
|
|
109
|
+
/** @deprecated Use `childrenObservers` */
|
|
110
|
+
get childWatchers() {
|
|
111
|
+
return this.childrenObservers;
|
|
112
|
+
}
|
|
113
|
+
constructor(watcher, freeParent, fireImmediately) {
|
|
114
|
+
super();
|
|
115
|
+
/** Indicates if computed value is stale and needs recalculation. */
|
|
116
|
+
this.invalid = true;
|
|
117
|
+
/** Tracks if the computation has run at least once. */
|
|
118
|
+
this.updated = false;
|
|
119
|
+
/**
|
|
120
|
+
* Indicates if observer has been destroyed.
|
|
121
|
+
* Prevents accidental use after cleanup.
|
|
122
|
+
*/
|
|
123
|
+
this.destroyed = false;
|
|
124
|
+
// TODO: remove in major release
|
|
125
|
+
/** @deprecated Use `observer instanceof Compute` */
|
|
126
|
+
this.isCache = true;
|
|
127
|
+
/** Cleanup functions to run on destroy (e.g., unsubscribes). */
|
|
128
|
+
this.destructors = new Set();
|
|
129
|
+
/** Child watchers created within this watcher's scope */
|
|
130
|
+
this.childrenObservers = new Set();
|
|
131
|
+
this.watcher = watcher;
|
|
132
|
+
if (!freeParent) {
|
|
133
|
+
bindObserver.bindObserver(this);
|
|
134
|
+
}
|
|
135
|
+
if (fireImmediately) {
|
|
136
|
+
this.forceUpdate();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Mark computation as invalid and trigger propagation to parent observers. */
|
|
140
|
+
update() {
|
|
141
|
+
invalidateCompute(this);
|
|
142
|
+
const parents = [...this.observers];
|
|
143
|
+
let parent;
|
|
144
|
+
while ((parent = parents.pop())) {
|
|
145
|
+
if (!(parent instanceof Compute)) {
|
|
146
|
+
return this.forceUpdate();
|
|
147
|
+
}
|
|
148
|
+
parents.push(...parent.observers);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
forceUpdate() {
|
|
152
|
+
if (!this.destroyed) {
|
|
153
|
+
this.invalid = false;
|
|
154
|
+
watchWithScope.watchWithScope(this, () => {
|
|
155
|
+
const newValue = this.watcher(this.updated ? this.updated = true : false);
|
|
156
|
+
if (newValue !== this.rawValue) {
|
|
157
|
+
this.rawValue = newValue;
|
|
158
|
+
queueWatchers(this.observers);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Current value with automatic subscription.
|
|
165
|
+
*
|
|
166
|
+
* Accessing `value` inside an `Observer` automatically subscribes the watcher.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* const count = new State(0)
|
|
170
|
+
* const text = new Compute(() => `Count: ${count.value}`)
|
|
171
|
+
*
|
|
172
|
+
* new Watch(() => console.log(text.value)) // Count: 0
|
|
173
|
+
*
|
|
174
|
+
* count.value++ // Count: 1
|
|
175
|
+
*/
|
|
176
|
+
get value() {
|
|
177
|
+
if (this.invalid) {
|
|
178
|
+
this.forceUpdate();
|
|
179
|
+
}
|
|
180
|
+
return this.destroyed ? this.rawValue : super.value;
|
|
181
|
+
}
|
|
182
|
+
/** Stop observation and remove all dependencies. */
|
|
183
|
+
destroy() {
|
|
184
|
+
destroyWatchers.destroyWatchers(this);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exports.Compute = Compute;
|
|
189
|
+
exports.forceQueueWatchers = forceQueueWatchers;
|
|
190
|
+
exports.invalidateCompute = invalidateCompute;
|
|
191
|
+
exports.queueWatchers = queueWatchers;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Compute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Compute, forceQueueWatchers, invalidateCompute, queueWatchers } from './Compute.es6.js';
|
package/Compute/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var Compute = require('./Compute.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
exports.Compute = Compute.Compute;
|
|
10
|
+
exports.forceQueueWatchers = Compute.forceQueueWatchers;
|
|
11
|
+
exports.invalidateCompute = Compute.invalidateCompute;
|
|
12
|
+
exports.queueWatchers = Compute.queueWatchers;
|