@zeix/cause-effect 0.15.2 → 0.16.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.
@@ -1,6 +1,6 @@
1
+ import { Random } from 'random'
1
2
  import type { TestConfig } from './framework-types'
2
3
  import type { Computed, ReactiveFramework, Signal } from './reactive-framework'
3
- import { Random } from 'random'
4
4
 
5
5
  export interface Graph {
6
6
  sources: Signal<number>[]
package/types/index.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * @name Cause & Effect
3
- * @version 0.15.1
3
+ * @version 0.16.1
4
4
  * @author Esther Brunner
5
5
  */
6
- export { type Computed, type ComputedCallback, computed, isComputed, isComputedCallback, TYPE_COMPUTED, } from './src/computed';
7
- export { type DiffResult, diff, isEqual, type UnknownArray, type UnknownRecord, type UnknownRecordOrArray, } from './src/diff';
8
- export { type EffectCallback, effect, type MaybeCleanup } from './src/effect';
9
- export { CircularDependencyError, InvalidSignalValueError, NullishSignalValueError, StoreKeyExistsError, StoreKeyRangeError, StoreKeyReadonlyError, } from './src/errors';
6
+ export { type Computed, type ComputedCallback, createComputed, isComputed, isComputedCallback, TYPE_COMPUTED, } from './src/computed';
7
+ export { type DiffResult, diff, isEqual, type PartialRecord, type UnknownArray, type UnknownRecord, } from './src/diff';
8
+ export { createEffect, type EffectCallback, type MaybeCleanup, } from './src/effect';
9
+ export { CircularDependencyError, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, StoreKeyExistsError, StoreKeyRangeError, StoreKeyReadonlyError, } from './src/errors';
10
10
  export { type MatchHandlers, match } from './src/match';
11
11
  export { type ResolveResult, resolve } from './src/resolve';
12
- export { batch, type Cleanup, enqueue, flush, notify, observe, subscribe, type Updater, type Watcher, watch, } from './src/scheduler';
13
12
  export { isMutableSignal, isSignal, type Signal, type SignalValues, toSignal, type UnknownSignalRecord, } from './src/signal';
14
- export { isState, type State, state, TYPE_STATE } from './src/state';
15
- export { isStore, type Store, type StoreAddEvent, type StoreChangeEvent, type StoreEventMap, type StoreRemoveEvent, type StoreSortEvent, store, TYPE_STORE, } from './src/store';
16
- export { isAbortError, isAsyncFunction, isFunction, isNumber, isRecord, isRecordOrArray, isString, isSymbol, toError, UNSET, } from './src/util';
13
+ export { createState, isState, type State, TYPE_STATE } from './src/state';
14
+ export { createStore, isStore, type Store, type StoreChanges, TYPE_STORE, } from './src/store';
15
+ export { batch, type Cleanup, createWatcher, flush, notify, observe, subscribe, type Watcher, } from './src/system';
16
+ export { isAbortError, isAsyncFunction, isFunction, isNumber, isObjectOfType, isRecord, isRecordOrArray, isString, isSymbol, toError, UNSET, valueString, } from './src/util';
@@ -0,0 +1,26 @@
1
+ import type { UnknownArray } from './diff';
2
+ import { type Store, type StoreChanges } from './store';
3
+ import { type Cleanup } from './system';
4
+ type Collection<T extends UnknownArray> = {
5
+ readonly [Symbol.toStringTag]: 'Collection';
6
+ get(): T;
7
+ on<K extends keyof StoreChanges<T>>(type: K, listener: (change: StoreChanges<T>[K]) => void): Cleanup;
8
+ };
9
+ type CollectionCallback<T extends UnknownArray> = (store: Store<T>) => T;
10
+ declare const TYPE_COLLECTION = "Collection";
11
+ /**
12
+ * Create a collection signal
13
+ *
14
+ * @param {CollectionCallback<T>} fn - callback function to create the collection
15
+ * @returns {Collection<T>} - collection signal
16
+ */
17
+ declare const createCollection: <T extends UnknownArray>(fn: CollectionCallback<T>) => Collection<T>;
18
+ /**
19
+ * Check if a value is a collection signal
20
+ *
21
+ * @since 0.16.0
22
+ * @param {unknown} value - value to check
23
+ * @returns {boolean} - true if value is a computed state, false otherwise
24
+ */
25
+ declare const isCollection: <T extends UnknownArray>(value: unknown) => value is Collection<T>;
26
+ export { TYPE_COLLECTION, createCollection, isCollection, type Collection };
@@ -1,33 +1,33 @@
1
1
  type Computed<T extends {}> = {
2
- [Symbol.toStringTag]: 'Computed';
2
+ readonly [Symbol.toStringTag]: 'Computed';
3
3
  get(): T;
4
4
  };
5
5
  type ComputedCallback<T extends {} & {
6
6
  then?: undefined;
7
- }> = ((abort: AbortSignal) => Promise<T>) | (() => T);
7
+ }> = ((oldValue: T, abort: AbortSignal) => Promise<T>) | ((oldValue: T) => T);
8
8
  declare const TYPE_COMPUTED = "Computed";
9
9
  /**
10
10
  * Create a derived signal from existing signals
11
11
  *
12
12
  * @since 0.9.0
13
- * @param {ComputedCallback<T>} fn - computation callback function
13
+ * @param {ComputedCallback<T>} callback - Computation callback function
14
14
  * @returns {Computed<T>} - Computed signal
15
15
  */
16
- declare const computed: <T extends {}>(fn: ComputedCallback<T>) => Computed<T>;
16
+ declare const createComputed: <T extends {}>(callback: ComputedCallback<T>, initialValue?: T) => Computed<T>;
17
17
  /**
18
- * Check if a value is a computed state
18
+ * Check if a value is a computed signal
19
19
  *
20
20
  * @since 0.9.0
21
- * @param {unknown} value - value to check
22
- * @returns {boolean} - true if value is a computed state, false otherwise
21
+ * @param {unknown} value - Value to check
22
+ * @returns {boolean} - true if value is a computed signal, false otherwise
23
23
  */
24
24
  declare const isComputed: <T extends {}>(value: unknown) => value is Computed<T>;
25
25
  /**
26
26
  * Check if the provided value is a callback that may be used as input for toSignal() to derive a computed state
27
27
  *
28
28
  * @since 0.12.0
29
- * @param {unknown} value - value to check
29
+ * @param {unknown} value - Value to check
30
30
  * @returns {boolean} - true if value is a callback or callbacks object, false otherwise
31
31
  */
32
32
  declare const isComputedCallback: <T extends {}>(value: unknown) => value is ComputedCallback<T>;
33
- export { TYPE_COMPUTED, computed, isComputed, isComputedCallback, type Computed, type ComputedCallback, };
33
+ export { TYPE_COMPUTED, createComputed, isComputed, isComputedCallback, type Computed, type ComputedCallback, };
@@ -3,12 +3,12 @@ type UnknownArray = ReadonlyArray<unknown & {}>;
3
3
  type ArrayToRecord<T extends UnknownArray> = {
4
4
  [key: string]: T extends Array<infer U extends {}> ? U : never;
5
5
  };
6
- type UnknownRecordOrArray = UnknownRecord | ArrayToRecord<UnknownArray>;
7
- type DiffResult<T extends UnknownRecordOrArray = UnknownRecord> = {
6
+ type PartialRecord<T> = T extends UnknownArray ? Partial<ArrayToRecord<T>> : Partial<T>;
7
+ type DiffResult<T extends UnknownRecord | UnknownArray = UnknownRecord> = {
8
8
  changed: boolean;
9
- add: Partial<T>;
10
- change: Partial<T>;
11
- remove: Partial<T>;
9
+ add: PartialRecord<T>;
10
+ change: PartialRecord<T>;
11
+ remove: PartialRecord<T>;
12
12
  };
13
13
  /**
14
14
  * Checks if two values are equal with cycle detection
@@ -28,5 +28,5 @@ declare const isEqual: <T>(a: T, b: T, visited?: WeakSet<object>) => boolean;
28
28
  * @param {T} newObj - The new record to compare
29
29
  * @returns {DiffResult<T>} The result of the comparison
30
30
  */
31
- declare const diff: <T extends UnknownRecordOrArray>(oldObj: T, newObj: T) => DiffResult<T>;
32
- export { type ArrayToRecord, type DiffResult, diff, isEqual, type UnknownRecord, type UnknownArray, type UnknownRecordOrArray, };
31
+ declare const diff: <T extends UnknownRecord | UnknownArray>(oldObj: T extends UnknownArray ? ArrayToRecord<T> : T, newObj: T extends UnknownArray ? ArrayToRecord<T> : T) => DiffResult<T>;
32
+ export { type ArrayToRecord, type DiffResult, diff, isEqual, type UnknownRecord, type UnknownArray, type PartialRecord, };
@@ -1,4 +1,4 @@
1
- import { type Cleanup } from './scheduler';
1
+ import { type Cleanup } from './system';
2
2
  type MaybeCleanup = Cleanup | undefined | void;
3
3
  type EffectCallback = (() => MaybeCleanup) | ((abort: AbortSignal) => Promise<MaybeCleanup>);
4
4
  /**
@@ -12,5 +12,5 @@ type EffectCallback = (() => MaybeCleanup) | ((abort: AbortSignal) => Promise<Ma
12
12
  * @param {EffectCallback} callback - Synchronous or asynchronous effect callback
13
13
  * @returns {Cleanup} - Cleanup function for the effect
14
14
  */
15
- declare const effect: (callback: EffectCallback) => Cleanup;
16
- export { type MaybeCleanup, type EffectCallback, effect };
15
+ declare const createEffect: (callback: EffectCallback) => Cleanup;
16
+ export { type MaybeCleanup, type EffectCallback, createEffect };
@@ -1,6 +1,9 @@
1
1
  declare class CircularDependencyError extends Error {
2
2
  constructor(where: string);
3
3
  }
4
+ declare class InvalidCallbackError extends TypeError {
5
+ constructor(where: string, value: string);
6
+ }
4
7
  declare class InvalidSignalValueError extends TypeError {
5
8
  constructor(where: string, value: string);
6
9
  }
@@ -16,4 +19,4 @@ declare class StoreKeyRangeError extends RangeError {
16
19
  declare class StoreKeyReadonlyError extends Error {
17
20
  constructor(key: string, value: string);
18
21
  }
19
- export { CircularDependencyError, InvalidSignalValueError, NullishSignalValueError, StoreKeyExistsError, StoreKeyRangeError, StoreKeyReadonlyError, };
22
+ export { CircularDependencyError, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, StoreKeyExistsError, StoreKeyRangeError, StoreKeyReadonlyError, };
@@ -1,8 +1,8 @@
1
1
  type State<T extends {}> = {
2
- [Symbol.toStringTag]: 'State';
2
+ readonly [Symbol.toStringTag]: 'State';
3
3
  get(): T;
4
- set(v: T): void;
5
- update(fn: (v: T) => T): void;
4
+ set(newValue: T): void;
5
+ update(updater: (oldValue: T) => T): void;
6
6
  };
7
7
  declare const TYPE_STATE = "State";
8
8
  /**
@@ -12,7 +12,7 @@ declare const TYPE_STATE = "State";
12
12
  * @param {T} initialValue - initial value of the state
13
13
  * @returns {State<T>} - new state signal
14
14
  */
15
- declare const state: <T extends {}>(initialValue: T) => State<T>;
15
+ declare const createState: <T extends {}>(initialValue: T) => State<T>;
16
16
  /**
17
17
  * Check if the provided value is a State instance
18
18
  *
@@ -21,4 +21,4 @@ declare const state: <T extends {}>(initialValue: T) => State<T>;
21
21
  * @returns {boolean} - true if the value is a State instance, false otherwise
22
22
  */
23
23
  declare const isState: <T extends {}>(value: unknown) => value is State<T>;
24
- export { TYPE_STATE, isState, state, type State };
24
+ export { TYPE_STATE, isState, createState, type State };
@@ -1,60 +1,45 @@
1
- import { type UnknownArray, type UnknownRecord, type UnknownRecordOrArray } from './diff';
1
+ import { type PartialRecord, type UnknownArray, type UnknownRecord } from './diff';
2
2
  import { type State } from './state';
3
+ import { type Cleanup } from './system';
3
4
  type ArrayItem<T> = T extends readonly (infer U extends {})[] ? U : never;
4
- type StoreEventMap<T extends UnknownRecord | UnknownArray> = {
5
- 'store-add': StoreAddEvent<T>;
6
- 'store-change': StoreChangeEvent<T>;
7
- 'store-remove': StoreRemoveEvent<T>;
8
- 'store-sort': StoreSortEvent;
5
+ type StoreChanges<T> = {
6
+ add: PartialRecord<T>;
7
+ change: PartialRecord<T>;
8
+ remove: PartialRecord<T>;
9
+ sort: string[];
9
10
  };
10
- interface StoreEventTarget<T extends UnknownRecord | UnknownArray> extends EventTarget {
11
- addEventListener<K extends keyof StoreEventMap<T>>(type: K, listener: (event: StoreEventMap<T>[K]) => void, options?: boolean | AddEventListenerOptions): void;
12
- removeEventListener<K extends keyof StoreEventMap<T>>(type: K, listener: (event: StoreEventMap<T>[K]) => void, options?: boolean | EventListenerOptions): void;
13
- dispatchEvent(event: Event): boolean;
14
- }
15
- interface BaseStore<T extends UnknownRecord | UnknownArray> extends StoreEventTarget<T> {
11
+ interface BaseStore {
16
12
  readonly [Symbol.toStringTag]: 'Store';
17
- get(): T;
18
- set(value: T): void;
19
- update(fn: (value: T) => T): void;
20
- sort<U = T extends UnknownArray ? ArrayItem<T> : T[Extract<keyof T, string>]>(compareFn?: (a: U, b: U) => number): void;
21
- readonly size: State<number>;
13
+ readonly length: number;
22
14
  }
23
- type RecordStore<T extends UnknownRecord> = BaseStore<T> & {
15
+ type RecordStore<T extends UnknownRecord> = BaseStore & {
24
16
  [K in keyof T]: T[K] extends readonly unknown[] | Record<string, unknown> ? Store<T[K]> : State<T[K]>;
25
17
  } & {
26
- add<K extends Extract<keyof T, string>>(key: K, value: T[K]): void;
27
- remove<K extends Extract<keyof T, string>>(key: K): void;
28
18
  [Symbol.iterator](): IterableIterator<[
29
19
  Extract<keyof T, string>,
30
20
  T[Extract<keyof T, string>] extends readonly unknown[] | Record<string, unknown> ? Store<T[Extract<keyof T, string>]> : State<T[Extract<keyof T, string>]>
31
21
  ]>;
22
+ add<K extends Extract<keyof T, string>>(key: K, value: T[K]): void;
23
+ get(): T;
24
+ set(value: T): void;
25
+ update(fn: (value: T) => T): void;
26
+ sort<U = T[Extract<keyof T, string>]>(compareFn?: (a: U, b: U) => number): void;
27
+ on<K extends keyof StoreChanges<T>>(type: K, listener: (change: StoreChanges<T>[K]) => void): Cleanup;
28
+ remove<K extends Extract<keyof T, string>>(key: K): void;
32
29
  };
33
- type ArrayStore<T extends UnknownArray> = BaseStore<T> & {
34
- readonly length: number;
30
+ type ArrayStore<T extends UnknownArray> = BaseStore & {
31
+ [Symbol.iterator](): IterableIterator<ArrayItem<T> extends readonly unknown[] | Record<string, unknown> ? Store<ArrayItem<T>> : State<ArrayItem<T>>>;
32
+ readonly [Symbol.isConcatSpreadable]: boolean;
35
33
  [n: number]: ArrayItem<T> extends readonly unknown[] | Record<string, unknown> ? Store<ArrayItem<T>> : State<ArrayItem<T>>;
36
34
  add(value: ArrayItem<T>): void;
35
+ get(): T;
36
+ set(value: T): void;
37
+ update(fn: (value: T) => T): void;
38
+ sort<U = ArrayItem<T>>(compareFn?: (a: U, b: U) => number): void;
39
+ on<K extends keyof StoreChanges<T>>(type: K, listener: (change: StoreChanges<T>[K]) => void): Cleanup;
37
40
  remove(index: number): void;
38
- [Symbol.iterator](): IterableIterator<ArrayItem<T> extends readonly unknown[] | Record<string, unknown> ? Store<ArrayItem<T>> : State<ArrayItem<T>>>;
39
- readonly [Symbol.isConcatSpreadable]: boolean;
40
41
  };
41
- interface StoreAddEvent<T extends UnknownRecord | UnknownArray> extends CustomEvent {
42
- type: 'store-add';
43
- detail: Partial<T>;
44
- }
45
- interface StoreChangeEvent<T extends UnknownRecord | UnknownArray> extends CustomEvent {
46
- type: 'store-change';
47
- detail: Partial<T>;
48
- }
49
- interface StoreRemoveEvent<T extends UnknownRecord | UnknownArray> extends CustomEvent {
50
- type: 'store-remove';
51
- detail: Partial<T>;
52
- }
53
- interface StoreSortEvent extends CustomEvent {
54
- type: 'store-sort';
55
- detail: string[];
56
- }
57
- type Store<T> = T extends UnknownRecord ? RecordStore<T> : T extends UnknownArray ? ArrayStore<T> : never;
42
+ type Store<T extends UnknownRecord | UnknownArray> = T extends UnknownRecord ? RecordStore<T> : T extends UnknownArray ? ArrayStore<T> : never;
58
43
  declare const TYPE_STORE = "Store";
59
44
  /**
60
45
  * Create a new store with deeply nested reactive properties
@@ -68,7 +53,7 @@ declare const TYPE_STORE = "Store";
68
53
  * @param {T} initialValue - initial object or array value of the store
69
54
  * @returns {Store<T>} - new store with reactive properties that preserves the original type T
70
55
  */
71
- declare const store: <T extends UnknownRecord | UnknownArray>(initialValue: T) => Store<T>;
56
+ declare const createStore: <T extends UnknownRecord | UnknownArray>(initialValue: T) => Store<T>;
72
57
  /**
73
58
  * Check if the provided value is a Store instance
74
59
  *
@@ -76,5 +61,5 @@ declare const store: <T extends UnknownRecord | UnknownArray>(initialValue: T) =
76
61
  * @param {unknown} value - value to check
77
62
  * @returns {boolean} - true if the value is a Store instance, false otherwise
78
63
  */
79
- declare const isStore: <T extends UnknownRecordOrArray>(value: unknown) => value is Store<T>;
80
- export { TYPE_STORE, isStore, store, type Store, type StoreAddEvent, type StoreChangeEvent, type StoreRemoveEvent, type StoreSortEvent, type StoreEventMap, };
64
+ declare const isStore: <T extends UnknownRecord | UnknownArray>(value: unknown) => value is Store<T>;
65
+ export { TYPE_STORE, isStore, createStore, type Store, type StoreChanges };
@@ -0,0 +1,44 @@
1
+ type Cleanup = () => void;
2
+ type Watcher = {
3
+ (): void;
4
+ unwatch(cleanup: Cleanup): void;
5
+ cleanup(): void;
6
+ };
7
+ /**
8
+ * Create a watcher that can be used to observe changes to a signal
9
+ *
10
+ * @since 0.14.1
11
+ * @param {() => void} watch - Function to be called when the state changes
12
+ * @returns {Watcher} - Watcher object with off and cleanup methods
13
+ */
14
+ declare const createWatcher: (watch: () => void) => Watcher;
15
+ /**
16
+ * Add active watcher to the Set of watchers
17
+ *
18
+ * @param {Set<Watcher>} watchers - watchers of the signal
19
+ */
20
+ declare const subscribe: (watchers: Set<Watcher>) => void;
21
+ /**
22
+ * Add watchers to the pending set of change notifications
23
+ *
24
+ * @param {Set<Watcher>} watchers - watchers of the signal
25
+ */
26
+ declare const notify: (watchers: Set<Watcher>) => void;
27
+ /**
28
+ * Flush all pending changes to notify watchers
29
+ */
30
+ declare const flush: () => void;
31
+ /**
32
+ * Batch multiple changes in a single signal graph and DOM update cycle
33
+ *
34
+ * @param {() => void} fn - function with multiple signal writes to be batched
35
+ */
36
+ declare const batch: (fn: () => void) => void;
37
+ /**
38
+ * Run a function in a reactive context
39
+ *
40
+ * @param {() => void} run - function to run the computation or effect
41
+ * @param {Watcher} watcher - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
42
+ */
43
+ declare const observe: (run: () => void, watcher?: Watcher) => void;
44
+ export { type Cleanup, type Watcher, subscribe, notify, flush, batch, createWatcher, observe, };
@@ -4,7 +4,6 @@ declare const isNumber: (value: unknown) => value is number;
4
4
  declare const isSymbol: (value: unknown) => value is symbol;
5
5
  declare const isFunction: <T>(fn: unknown) => fn is (...args: unknown[]) => T;
6
6
  declare const isAsyncFunction: <T>(fn: unknown) => fn is (...args: unknown[]) => Promise<T>;
7
- declare const isDefinedObject: (value: unknown) => value is Record<string, unknown>;
8
7
  declare const isObjectOfType: <T>(value: unknown, type: string) => value is T;
9
8
  declare const isRecord: <T extends Record<string, unknown>>(value: unknown) => value is T;
10
9
  declare const isRecordOrArray: <T extends Record<string | number, unknown> | ReadonlyArray<unknown>>(value: unknown) => value is T;
@@ -14,4 +13,4 @@ declare const toError: (reason: unknown) => Error;
14
13
  declare const arrayToRecord: <T>(array: T[]) => Record<string, T>;
15
14
  declare const recordToArray: <T>(record: Record<string | number, T>) => Record<string, T> | T[];
16
15
  declare const valueString: (value: unknown) => string;
17
- export { UNSET, isString, isNumber, isSymbol, isFunction, isAsyncFunction, isDefinedObject, isObjectOfType, isRecord, isRecordOrArray, hasMethod, isAbortError, toError, arrayToRecord, recordToArray, valueString, };
16
+ export { UNSET, isString, isNumber, isSymbol, isFunction, isAsyncFunction, isObjectOfType, isRecord, isRecordOrArray, hasMethod, isAbortError, toError, arrayToRecord, recordToArray, valueString, };
package/src/scheduler.ts DELETED
@@ -1,172 +0,0 @@
1
- /* === Types === */
2
-
3
- type Cleanup = () => void
4
-
5
- type Watcher = {
6
- (): void
7
- off(cleanup: Cleanup): void
8
- cleanup(): void
9
- }
10
-
11
- type Updater = <T>() => T | boolean | undefined
12
-
13
- /* === Internal === */
14
-
15
- // Currently active watcher
16
- let active: Watcher | undefined
17
-
18
- // Pending queue for batched change notifications
19
- const pending = new Set<Watcher>()
20
- let batchDepth = 0
21
-
22
- // Map of deduplication symbols to update functions (using Symbol keys prevents unintended overwrites)
23
- const updateMap = new Map<symbol, Updater>()
24
- let requestId: number | undefined
25
-
26
- const updateDOM = () => {
27
- requestId = undefined
28
- const updates = Array.from(updateMap.values())
29
- updateMap.clear()
30
- for (const update of updates) {
31
- update()
32
- }
33
- }
34
-
35
- const requestTick = () => {
36
- if (requestId) cancelAnimationFrame(requestId)
37
- requestId = requestAnimationFrame(updateDOM)
38
- }
39
-
40
- // Initial render when the call stack is empty
41
- queueMicrotask(updateDOM)
42
-
43
- /* === Functions === */
44
-
45
- /**
46
- * Create a watcher that can be used to observe changes to a signal
47
- *
48
- * @since 0.14.1
49
- * @param {() => void} notice - function to be called when the state changes
50
- * @returns {Watcher} - watcher object with off and cleanup methods
51
- */
52
- const watch = (notice: () => void): Watcher => {
53
- const cleanups = new Set<Cleanup>()
54
- const w = notice as Partial<Watcher>
55
- w.off = (on: Cleanup) => {
56
- cleanups.add(on)
57
- }
58
- w.cleanup = () => {
59
- for (const cleanup of cleanups) {
60
- cleanup()
61
- }
62
- cleanups.clear()
63
- }
64
- return w as Watcher
65
- }
66
-
67
- /**
68
- * Add active watcher to the Set of watchers
69
- *
70
- * @param {Set<Watcher>} watchers - watchers of the signal
71
- */
72
- const subscribe = (watchers: Set<Watcher>) => {
73
- if (active && !watchers.has(active)) {
74
- const watcher = active
75
- watchers.add(watcher)
76
- active.off(() => {
77
- watchers.delete(watcher)
78
- })
79
- }
80
- }
81
-
82
- /**
83
- * Add watchers to the pending set of change notifications
84
- *
85
- * @param {Set<Watcher>} watchers - watchers of the signal
86
- */
87
- const notify = (watchers: Set<Watcher>) => {
88
- for (const watcher of watchers) {
89
- if (batchDepth) pending.add(watcher)
90
- else watcher()
91
- }
92
- }
93
-
94
- /**
95
- * Flush all pending changes to notify watchers
96
- */
97
- const flush = () => {
98
- while (pending.size) {
99
- const watchers = Array.from(pending)
100
- pending.clear()
101
- for (const watcher of watchers) {
102
- watcher()
103
- }
104
- }
105
- }
106
-
107
- /**
108
- * Batch multiple changes in a single signal graph and DOM update cycle
109
- *
110
- * @param {() => void} fn - function with multiple signal writes to be batched
111
- */
112
- const batch = (fn: () => void) => {
113
- batchDepth++
114
- try {
115
- fn()
116
- } finally {
117
- flush()
118
- batchDepth--
119
- }
120
- }
121
-
122
- /**
123
- * Run a function in a reactive context
124
- *
125
- * @param {() => void} run - function to run the computation or effect
126
- * @param {Watcher} watcher - function to be called when the state changes or undefined for temporary unwatching while inserting auto-hydrating DOM nodes that might read signals (e.g., web components)
127
- */
128
- const observe = (run: () => void, watcher?: Watcher): void => {
129
- const prev = active
130
- active = watcher
131
- try {
132
- run()
133
- } finally {
134
- active = prev
135
- }
136
- }
137
-
138
- /**
139
- * Enqueue a function to be executed on the next animation frame
140
- *
141
- * If the same Symbol is provided for multiple calls before the next animation frame,
142
- * only the latest call will be executed (deduplication).
143
- *
144
- * @param {Updater} fn - function to be executed on the next animation frame; can return updated value <T>, success <boolean> or void
145
- * @param {symbol} dedupe - Symbol for deduplication; if not provided, a unique Symbol is created ensuring the update is always executed
146
- */
147
- const enqueue = <T>(fn: Updater, dedupe?: symbol) =>
148
- new Promise<T | boolean | undefined>((resolve, reject) => {
149
- updateMap.set(dedupe || Symbol(), (): undefined => {
150
- try {
151
- resolve(fn())
152
- } catch (error) {
153
- reject(error)
154
- }
155
- })
156
- requestTick()
157
- })
158
-
159
- /* === Exports === */
160
-
161
- export {
162
- type Cleanup,
163
- type Watcher,
164
- type Updater,
165
- subscribe,
166
- notify,
167
- flush,
168
- batch,
169
- watch,
170
- observe,
171
- enqueue,
172
- }