@zakkster/lite-signal 1.0.5 → 1.0.6
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/Signal.d.ts +34 -0
- package/Signal.js +7 -1
- package/Watch.js +72 -0
- package/package.json +2 -1
package/Signal.d.ts
CHANGED
|
@@ -165,3 +165,37 @@ export function batch<T>(fn: () => T): T;
|
|
|
165
165
|
export function untrack<T>(fn: () => T): T;
|
|
166
166
|
export function onCleanup(fn: () => void): void;
|
|
167
167
|
export function stats(): RegistryStats;
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Configuration options for the watch utility.
|
|
172
|
+
*/
|
|
173
|
+
export interface WatchOptions {
|
|
174
|
+
/** * If true, fires the callback immediately upon registration
|
|
175
|
+
* with `oldValue` set to `undefined`.
|
|
176
|
+
*/
|
|
177
|
+
immediate?: boolean;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Track a reactive source and run a callback whenever its evaluated value changes.
|
|
182
|
+
*
|
|
183
|
+
* Models Vue's `watch(source, callback)` and MobX's `reaction(predicate, effect)`.
|
|
184
|
+
* Internal reads inside the callback are untracked — they do not create reactive
|
|
185
|
+
* dependencies.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const count = signal(0);
|
|
189
|
+
* const stop = watch(() => count() * 2, (next, prev) => {
|
|
190
|
+
* console.log(`Doubled count changed: ${prev} -> ${next}`);
|
|
191
|
+
* });
|
|
192
|
+
* * @param source A function that reads reactive values (e.g., a signal/computed getter).
|
|
193
|
+
* @param callback Fired when the source's value changes. Receives the new and previous values.
|
|
194
|
+
* @param options Optional configuration (e.g., `{ immediate: true }`).
|
|
195
|
+
* @returns Dispose function — call to stop watching and release the effect.
|
|
196
|
+
*/
|
|
197
|
+
export function watch<T>(
|
|
198
|
+
source: () => T,
|
|
199
|
+
callback: (newValue: T, oldValue: T | undefined) => void,
|
|
200
|
+
options?: WatchOptions
|
|
201
|
+
): () => void;
|
package/Signal.js
CHANGED
|
@@ -1004,4 +1004,10 @@ export function onCleanup(fn) {
|
|
|
1004
1004
|
/** @type {Registry["stats"]} */
|
|
1005
1005
|
export function stats() {
|
|
1006
1006
|
return defaultRegistry.stats();
|
|
1007
|
-
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Re-export of the user-land watch utility.
|
|
1011
|
+
* @see {@link watch} in Watch.js for full implementation details.
|
|
1012
|
+
*/
|
|
1013
|
+
export {watch} from "./Watch.js"
|
package/Watch.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { effect, untrack } from "./Signal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel for "first run" — distinguishes a legitimate `undefined` source value
|
|
5
|
+
* from the uninitialized state. Using `Symbol` instead of `undefined` ensures a
|
|
6
|
+
* source like `signal(undefined)` correctly fires `callback(undefined, undefined)`
|
|
7
|
+
* on first change rather than being treated as never-changed.
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
const UNINITIALIZED = Symbol("watch.uninitialized");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Track a reactive source and run a callback whenever its value changes.
|
|
14
|
+
*
|
|
15
|
+
* Models Vue's `watch(source, callback)` and MobX's `reaction(predicate, effect)`.
|
|
16
|
+
* The callback is invoked with `(newValue, oldValue)`. Internal reads inside the
|
|
17
|
+
* callback are untracked — they don't create reactive dependencies — so a callback
|
|
18
|
+
* that reads other signals to perform a side-effect won't re-fire when those
|
|
19
|
+
* unrelated signals change.
|
|
20
|
+
*
|
|
21
|
+
* Disposing the returned function detaches the underlying effect and stops the
|
|
22
|
+
* watcher.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const count = signal(0);
|
|
26
|
+
* const stop = watch(count, (next, prev) => {
|
|
27
|
+
* console.log(`count changed: ${prev} -> ${next}`);
|
|
28
|
+
* });
|
|
29
|
+
* count.set(1); // logs: "count changed: 0 -> 1"
|
|
30
|
+
* count.set(2); // logs: "count changed: 1 -> 2"
|
|
31
|
+
* stop();
|
|
32
|
+
* count.set(3); // no log
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Immediate fires the callback once on registration with `oldValue = undefined`
|
|
36
|
+
* watch(count, (next, prev) => console.log(next), { immediate: true });
|
|
37
|
+
*
|
|
38
|
+
* @param {() => T} source A function that reads reactive values (typically a
|
|
39
|
+
* signal/computed getter, or a closure combining several).
|
|
40
|
+
* @param {(newValue: T, oldValue: T | undefined) => void} callback
|
|
41
|
+
* Called when the source's value changes. Receives the
|
|
42
|
+
* new and previous values. Internal reads are untracked.
|
|
43
|
+
* @param {{ immediate?: boolean }} [options]
|
|
44
|
+
* `immediate: true` runs the callback once on registration
|
|
45
|
+
* with `oldValue = undefined`. Defaults to false.
|
|
46
|
+
* @returns {() => void} Dispose function — call to stop watching.
|
|
47
|
+
* @template T
|
|
48
|
+
*/
|
|
49
|
+
export function watch(source, callback, options) {
|
|
50
|
+
const immediate = options !== undefined && options.immediate === true;
|
|
51
|
+
let oldValue = UNINITIALIZED;
|
|
52
|
+
|
|
53
|
+
return effect(() => {
|
|
54
|
+
// Track the source — this read registers the dependency.
|
|
55
|
+
const newValue = source();
|
|
56
|
+
|
|
57
|
+
// Invoke the callback without registering further dependencies.
|
|
58
|
+
untrack(() => {
|
|
59
|
+
if (oldValue === UNINITIALIZED) {
|
|
60
|
+
if (immediate) callback(newValue, undefined);
|
|
61
|
+
} else if (!Object.is(newValue, oldValue)) {
|
|
62
|
+
// Guard for raw inline getters: the effect re-runs whenever any read
|
|
63
|
+
// dep changes, but the projected source value may be unchanged. Vue's
|
|
64
|
+
// `watch` and MobX's `reaction` both short-circuit here. Wrapping the
|
|
65
|
+
// source in a `computed` would also suppress this via the equality
|
|
66
|
+
// check inside computed itself; the guard makes that wrapping optional.
|
|
67
|
+
callback(newValue, oldValue);
|
|
68
|
+
}
|
|
69
|
+
oldValue = newValue;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-signal",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Zero-GC reactive graph. Monomorphic object pool, versioned push-pull propagation, 32-bit modular versioning. Built for hot paths and long-running processes.",
|
|
5
5
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"Signal.js",
|
|
20
20
|
"Signal.d.ts",
|
|
21
|
+
"Watch.js",
|
|
21
22
|
"README.md",
|
|
22
23
|
"llms.txt",
|
|
23
24
|
"LICENSE.txt"
|