@hoosk/minisignals 1.0.4 → 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/README.md +10 -4
- package/dist/computed.d.ts +2 -3
- package/dist/computed.js +5 -3
- package/dist/effect.d.ts +6 -7
- package/dist/effect.js +74 -7
- package/dist/index.d.ts +0 -1
- package/dist/signal.d.ts +4 -2
- package/dist/signal.js +6 -5
- package/package.json +10 -2
- package/dist/computed.d.ts.map +0 -1
- package/dist/effect.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/signal.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @hoosk/minisignals
|
|
2
2
|
|
|
3
|
-
The framework-agnostic core of the `minisignals` library.
|
|
3
|
+
The framework-agnostic core of the `minisignals` library.
|
|
4
4
|
|
|
5
|
-
This package provides the fundamental reactive primitives: `signal`, `effect`, and `
|
|
5
|
+
This package provides the fundamental reactive primitives: `signal`, `effect`, `computed`, `batch`, and `untracked`. It has zero dependencies and is built for maximum performance and a minimal footprint.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ npm install @hoosk/minisignals
|
|
|
13
13
|
## Basic Usage
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import { signal, effect, computed } from '@hoosk/minisignals';
|
|
16
|
+
import { signal, effect, computed, batch, untracked } from '@hoosk/minisignals';
|
|
17
17
|
|
|
18
18
|
const count = signal(0);
|
|
19
19
|
const double = computed(() => count.value * 2);
|
|
@@ -23,6 +23,12 @@ effect(() => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
count.value = 1; // Logs: Count: 1, Double: 2
|
|
26
|
+
|
|
27
|
+
// Batch multiple writes into a single notification pass
|
|
28
|
+
batch(() => {
|
|
29
|
+
count.value = 10;
|
|
30
|
+
count.value = 20;
|
|
31
|
+
}); // Logs once: Count: 20, Double: 40
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
For full documentation and advanced features
|
|
34
|
+
For full documentation and advanced features, please refer to the [main repository README](../../README.md).
|
package/dist/computed.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { ReadonlySignal } from './signal';
|
|
2
|
+
export interface Computed<T> extends ReadonlySignal<T> {
|
|
3
3
|
/**
|
|
4
4
|
* Stops the internal effect from observing dependencies.
|
|
5
5
|
* Call this when the computed signal is no longer needed to prevent memory leaks.
|
|
@@ -14,4 +14,3 @@ export interface Computed<T> {
|
|
|
14
14
|
* @returns A read-only signal with a dispose method.
|
|
15
15
|
*/
|
|
16
16
|
export declare function computed<T>(fn: () => T): Computed<T>;
|
|
17
|
-
//# sourceMappingURL=computed.d.ts.map
|
package/dist/computed.js
CHANGED
|
@@ -9,14 +9,16 @@ import { effect } from './effect';
|
|
|
9
9
|
*/
|
|
10
10
|
export function computed(fn) {
|
|
11
11
|
let initialized = false;
|
|
12
|
-
let internalSignal;
|
|
12
|
+
let internalSignal = null;
|
|
13
13
|
let unsubscribe = null;
|
|
14
14
|
let isDisposed = false;
|
|
15
15
|
return {
|
|
16
16
|
get value() {
|
|
17
17
|
if (isDisposed) {
|
|
18
|
-
if (!initialized)
|
|
19
|
-
|
|
18
|
+
if (!initialized) {
|
|
19
|
+
internalSignal = signal(fn());
|
|
20
|
+
initialized = true;
|
|
21
|
+
}
|
|
20
22
|
return internalSignal.value;
|
|
21
23
|
}
|
|
22
24
|
if (!initialized) {
|
package/dist/effect.d.ts
CHANGED
|
@@ -2,13 +2,12 @@ export type SubscriberSet = Set<() => void>;
|
|
|
2
2
|
interface ActiveEffect {
|
|
3
3
|
run: () => void;
|
|
4
4
|
deps: Set<SubscriberSet>;
|
|
5
|
+
isRunning: boolean;
|
|
6
|
+
needsRerun: boolean;
|
|
5
7
|
}
|
|
6
|
-
export declare
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @returns An unsubscribe function.
|
|
11
|
-
*/
|
|
8
|
+
export declare function getActiveEffect(): ActiveEffect | null;
|
|
9
|
+
export declare function scheduleSubs(subscribers: SubscriberSet): void;
|
|
10
|
+
export declare function batch<T>(fn: () => T): T;
|
|
11
|
+
export declare function untracked<T>(fn: () => T): T;
|
|
12
12
|
export declare function effect(fn: () => void): () => void;
|
|
13
13
|
export {};
|
|
14
|
-
//# sourceMappingURL=effect.d.ts.map
|
package/dist/effect.js
CHANGED
|
@@ -1,29 +1,96 @@
|
|
|
1
|
-
|
|
1
|
+
let _activeEffect = null;
|
|
2
|
+
export function getActiveEffect() {
|
|
3
|
+
return _activeEffect;
|
|
4
|
+
}
|
|
5
|
+
// --- Batch scheduling ---
|
|
6
|
+
let _batchDepth = 0;
|
|
7
|
+
const _pendingNotifications = new Set();
|
|
8
|
+
export function scheduleSubs(subscribers) {
|
|
9
|
+
if (_batchDepth > 0) {
|
|
10
|
+
for (const sub of subscribers) {
|
|
11
|
+
_pendingNotifications.add(sub);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
for (const sub of [...subscribers]) {
|
|
16
|
+
sub();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function batch(fn) {
|
|
21
|
+
_batchDepth++;
|
|
22
|
+
try {
|
|
23
|
+
return fn();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
_batchDepth--;
|
|
27
|
+
if (_batchDepth === 0) {
|
|
28
|
+
const pending = [..._pendingNotifications];
|
|
29
|
+
_pendingNotifications.clear();
|
|
30
|
+
for (const sub of pending) {
|
|
31
|
+
sub();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// --- Untracked read ---
|
|
37
|
+
export function untracked(fn) {
|
|
38
|
+
const previous = _activeEffect;
|
|
39
|
+
_activeEffect = null;
|
|
40
|
+
try {
|
|
41
|
+
return fn();
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
_activeEffect = previous;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// --- Effect ---
|
|
2
48
|
/**
|
|
3
49
|
* Creates an effect that runs immediately and tracks dependencies.
|
|
4
50
|
* @param fn The function to execute.
|
|
5
51
|
* @returns An unsubscribe function.
|
|
6
52
|
*/
|
|
53
|
+
const MAX_EFFECT_ITERATIONS = 100;
|
|
7
54
|
export function effect(fn) {
|
|
8
55
|
const effectObj = {
|
|
9
56
|
run() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
57
|
+
if (effectObj.isRunning) {
|
|
58
|
+
effectObj.needsRerun = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
effectObj.isRunning = true;
|
|
62
|
+
let iterations = 0;
|
|
13
63
|
try {
|
|
14
|
-
|
|
64
|
+
do {
|
|
65
|
+
if (++iterations > MAX_EFFECT_ITERATIONS) {
|
|
66
|
+
throw new Error('Circular dependency detected: effect exceeded maximum re-run limit');
|
|
67
|
+
}
|
|
68
|
+
effectObj.needsRerun = false;
|
|
69
|
+
cleanup(effectObj);
|
|
70
|
+
const previousEffect = _activeEffect;
|
|
71
|
+
_activeEffect = effectObj;
|
|
72
|
+
try {
|
|
73
|
+
fn();
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
_activeEffect = previousEffect;
|
|
77
|
+
}
|
|
78
|
+
} while (effectObj.needsRerun);
|
|
15
79
|
}
|
|
16
80
|
finally {
|
|
17
|
-
|
|
81
|
+
effectObj.isRunning = false;
|
|
18
82
|
}
|
|
19
83
|
},
|
|
20
|
-
deps: new Set()
|
|
84
|
+
deps: new Set(),
|
|
85
|
+
isRunning: false,
|
|
86
|
+
needsRerun: false,
|
|
21
87
|
};
|
|
22
88
|
function cleanup(eff) {
|
|
23
89
|
for (const depSet of eff.deps) {
|
|
24
90
|
depSet.delete(eff.run);
|
|
25
91
|
}
|
|
26
92
|
eff.deps.clear();
|
|
93
|
+
_pendingNotifications.delete(eff.run);
|
|
27
94
|
}
|
|
28
95
|
effectObj.run();
|
|
29
96
|
return () => cleanup(effectObj);
|
package/dist/index.d.ts
CHANGED
package/dist/signal.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface ReadonlySignal<T> {
|
|
2
|
+
readonly value: T;
|
|
3
|
+
}
|
|
4
|
+
export interface Signal<T> extends ReadonlySignal<T> {
|
|
2
5
|
value: T;
|
|
3
6
|
}
|
|
4
7
|
/**
|
|
@@ -7,4 +10,3 @@ export interface Signal<T> {
|
|
|
7
10
|
* @returns An object with a reactive .value property.
|
|
8
11
|
*/
|
|
9
12
|
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
10
|
-
//# sourceMappingURL=signal.d.ts.map
|
package/dist/signal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getActiveEffect, scheduleSubs } from './effect';
|
|
2
2
|
/**
|
|
3
3
|
* Creates a reactive signal.
|
|
4
4
|
* @param initialValue The starting value.
|
|
@@ -9,16 +9,17 @@ export function signal(initialValue) {
|
|
|
9
9
|
let _value = initialValue;
|
|
10
10
|
return {
|
|
11
11
|
get value() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const active = getActiveEffect();
|
|
13
|
+
if (active) {
|
|
14
|
+
subscribers.add(active.run);
|
|
15
|
+
active.deps.add(subscribers);
|
|
15
16
|
}
|
|
16
17
|
return _value;
|
|
17
18
|
},
|
|
18
19
|
set value(newValue) {
|
|
19
20
|
if (_value !== newValue) {
|
|
20
21
|
_value = newValue;
|
|
21
|
-
|
|
22
|
+
scheduleSubs(subscribers);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoosk/minisignals",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Minimal agnostic signals library",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.
|
|
6
|
+
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"type": "module",
|
|
9
16
|
"files": [
|
|
10
17
|
"dist"
|
|
11
18
|
],
|
|
12
19
|
"scripts": {
|
|
13
20
|
"build": "tsc",
|
|
21
|
+
"prepare": "npm run build",
|
|
14
22
|
"test": "vitest run"
|
|
15
23
|
},
|
|
16
24
|
"keywords": [],
|
package/dist/computed.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../src/computed.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAmCpD"}
|
package/dist/effect.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;AAE5C,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,IAAI,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;CAC1B;AAED,eAAO,IAAI,YAAY,EAAE,YAAY,GAAG,IAAW,CAAC;AAEpD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAyBjD"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC"}
|
package/dist/signal.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC;CACV;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAmBpD"}
|