···11/**
22 * A reactive primitive that notifies subscribers when its value changes.
33- * Updates are batched in microtasks to avoid redundant notifications.
43 */
54export interface Signal<T> {
65 /**
···1091110 /**
1211 * Update the signal's value.
1313- * If the new value differs from the current value, subscribers will be notified
1414- * asynchronously in a batched microtask.
1212+ * If the new value differs from the current value, subscribers will be notified.
1513 */
1614 set(value: T): void;
17151816 /**
1917 * Subscribe to changes in the signal's value.
2018 * The callback is invoked with the new value whenever it changes.
1919+ * Returns an unsubscribe function to remove the subscription.
2020+ */
2121+ subscribe(callback: (value: T) => void): () => void;
2222+}
2323+2424+/**
2525+ * A computed signal that derives its value from other signals.
2626+ */
2727+export interface ComputedSignal<T> {
2828+ /**
2929+ * Get the current computed value.
3030+ */
3131+ get(): T;
3232+3333+ /**
3434+ * Subscribe to changes in the computed value.
2135 * Returns an unsubscribe function to remove the subscription.
2236 */
2337 subscribe(callback: (value: T) => void): () => void;
···7286 },
7387 };
7488}
8989+9090+/**
9191+ * Creates a computed signal that derives its value from other signals.
9292+ * The computation function is re-run whenever any of its dependencies change.
9393+ *
9494+ * @param compute - Function that computes the derived value
9595+ * @param dependencies - Array of signals this computation depends on
9696+ * @returns A ComputedSignal with get and subscribe methods
9797+ *
9898+ * @example
9999+ * const count = signal(5);
100100+ * const doubled = computed(() => count.get() * 2, [count]);
101101+ * doubled.get(); // 10
102102+ * count.set(10);
103103+ * doubled.get(); // 20
104104+ */
105105+export function computed<T>(
106106+ compute: () => T,
107107+ dependencies: Array<Signal<unknown> | ComputedSignal<unknown>>,
108108+): ComputedSignal<T> {
109109+ let value = compute();
110110+ const subscribers = new Set<(value: T) => void>();
111111+112112+ const notify = () => {
113113+ for (const callback of subscribers) {
114114+ try {
115115+ callback(value);
116116+ } catch (error) {
117117+ console.error("Error in computed subscriber:", error);
118118+ }
119119+ }
120120+ };
121121+122122+ const recompute = () => {
123123+ const newValue = compute();
124124+ if (value !== newValue) {
125125+ value = newValue;
126126+ notify();
127127+ }
128128+ };
129129+130130+ for (const dependency of dependencies) {
131131+ dependency.subscribe(recompute);
132132+ }
133133+134134+ return {
135135+ get() {
136136+ return value;
137137+ },
138138+139139+ subscribe(callback: (value: T) => void) {
140140+ subscribers.add(callback);
141141+142142+ return () => {
143143+ subscribers.delete(callback);
144144+ };
145145+ },
146146+ };
147147+}
148148+149149+/**
150150+ * Creates a side effect that runs when dependencies change.
151151+ * Effects run immediately on creation and whenever dependencies update.
152152+ *
153153+ * @param effectFunction - Function to run as a side effect
154154+ * @param dependencies - Array of signals this effect depends on
155155+ * @returns Cleanup function to stop the effect
156156+ *
157157+ * @example
158158+ * const count = signal(0);
159159+ * const cleanup = effect(() => {
160160+ * console.log('Count changed:', count.get());
161161+ * }, [count]);
162162+ */
163163+export function effect(
164164+ effectFunction: () => void | (() => void),
165165+ dependencies: Array<Signal<unknown> | ComputedSignal<unknown>>,
166166+): () => void {
167167+ let cleanup: (() => void) | void;
168168+169169+ const runEffect = () => {
170170+ if (cleanup) {
171171+ cleanup();
172172+ }
173173+ try {
174174+ cleanup = effectFunction();
175175+ } catch (error) {
176176+ console.error("Error in effect:", error);
177177+ }
178178+ };
179179+180180+ runEffect();
181181+182182+ const unsubscribers = dependencies.map((dependency) => dependency.subscribe(runEffect));
183183+184184+ return () => {
185185+ if (cleanup) {
186186+ cleanup();
187187+ }
188188+ for (const unsubscribe of unsubscribers) {
189189+ unsubscribe();
190190+ }
191191+ };
192192+}
+1-1
src/index.ts
···44 * @packageDocumentation
55 */
6677-export { signal, type Signal } from "./core/signal";
87export { mount } from "./core/binder";
88+export { computed, type ComputedSignal, effect, type Signal, signal } from "./core/signal";