@zeix/cause-effect 0.15.2 → 0.16.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/.ai-context.md +254 -0
- package/.cursorrules +54 -0
- package/.github/copilot-instructions.md +132 -0
- package/CLAUDE.md +319 -0
- package/README.md +136 -166
- package/eslint.config.js +1 -1
- package/index.dev.js +125 -129
- package/index.js +1 -1
- package/index.ts +22 -22
- package/package.json +1 -1
- package/src/computed.ts +40 -29
- package/src/effect.ts +15 -12
- package/src/errors.ts +8 -0
- package/src/signal.ts +6 -6
- package/src/state.ts +27 -20
- package/src/store.ts +99 -121
- package/src/system.ts +122 -0
- package/src/util.ts +1 -6
- package/test/batch.test.ts +18 -11
- package/test/benchmark.test.ts +4 -4
- package/test/computed.test.ts +507 -71
- package/test/effect.test.ts +60 -60
- package/test/match.test.ts +25 -25
- package/test/resolve.test.ts +16 -16
- package/test/signal.test.ts +7 -7
- package/test/state.test.ts +212 -25
- package/test/store.test.ts +476 -183
- package/test/util/dependency-graph.ts +1 -1
- package/types/index.d.ts +8 -8
- package/types/src/collection.d.ts +26 -0
- package/types/src/computed.d.ts +9 -9
- package/types/src/effect.d.ts +3 -3
- package/types/src/errors.d.ts +4 -1
- package/types/src/state.d.ts +5 -5
- package/types/src/store.d.ts +27 -41
- package/types/src/system.d.ts +44 -0
- package/types/src/util.d.ts +1 -2
- package/src/scheduler.ts +0 -172
package/types/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @name Cause & Effect
|
|
3
|
-
* @version 0.
|
|
3
|
+
* @version 0.16.0
|
|
4
4
|
* @author Esther Brunner
|
|
5
5
|
*/
|
|
6
|
-
export { type Computed, type ComputedCallback,
|
|
6
|
+
export { type Computed, type ComputedCallback, createComputed, isComputed, isComputedCallback, TYPE_COMPUTED, } from './src/computed';
|
|
7
7
|
export { type DiffResult, diff, isEqual, type UnknownArray, type UnknownRecord, type UnknownRecordOrArray, } from './src/diff';
|
|
8
|
-
export { type EffectCallback,
|
|
9
|
-
export { CircularDependencyError, InvalidSignalValueError, NullishSignalValueError, StoreKeyExistsError, StoreKeyRangeError, StoreKeyReadonlyError, } from './src/errors';
|
|
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,
|
|
15
|
-
export { isStore, type Store, type
|
|
16
|
-
export {
|
|
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, 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 };
|
package/types/src/computed.d.ts
CHANGED
|
@@ -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>}
|
|
13
|
+
* @param {ComputedCallback<T>} callback - Computation callback function
|
|
14
14
|
* @returns {Computed<T>} - Computed signal
|
|
15
15
|
*/
|
|
16
|
-
declare const
|
|
16
|
+
declare const createComputed: <T extends {}>(callback: ComputedCallback<T>, initialValue?: T) => Computed<T>;
|
|
17
17
|
/**
|
|
18
|
-
* Check if a value is a computed
|
|
18
|
+
* Check if a value is a computed signal
|
|
19
19
|
*
|
|
20
20
|
* @since 0.9.0
|
|
21
|
-
* @param {unknown} value -
|
|
22
|
-
* @returns {boolean} - true if value is a computed
|
|
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 -
|
|
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,
|
|
33
|
+
export { TYPE_COMPUTED, createComputed, isComputed, isComputedCallback, type Computed, type ComputedCallback, };
|
package/types/src/effect.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cleanup } from './
|
|
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
|
|
16
|
-
export { type MaybeCleanup, type EffectCallback,
|
|
15
|
+
declare const createEffect: (callback: EffectCallback) => Cleanup;
|
|
16
|
+
export { type MaybeCleanup, type EffectCallback, createEffect };
|
package/types/src/errors.d.ts
CHANGED
|
@@ -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, };
|
package/types/src/state.d.ts
CHANGED
|
@@ -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(
|
|
5
|
-
update(
|
|
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
|
|
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,
|
|
24
|
+
export { TYPE_STATE, isState, createState, type State };
|
package/types/src/store.d.ts
CHANGED
|
@@ -1,60 +1,46 @@
|
|
|
1
1
|
import { type UnknownArray, type UnknownRecord, type UnknownRecordOrArray } 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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
type StoreChanges<T> = {
|
|
6
|
+
add: Partial<T>;
|
|
7
|
+
change: Partial<T>;
|
|
8
|
+
remove: Partial<T>;
|
|
9
|
+
sort: string[];
|
|
9
10
|
};
|
|
10
|
-
interface
|
|
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
13
|
readonly size: State<number>;
|
|
22
14
|
}
|
|
23
|
-
type RecordStore<T extends UnknownRecord> = BaseStore
|
|
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
|
|
34
|
-
readonly
|
|
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
|
-
|
|
39
|
-
readonly [Symbol.isConcatSpreadable]: boolean;
|
|
41
|
+
readonly length: number;
|
|
40
42
|
};
|
|
41
|
-
|
|
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;
|
|
43
|
+
type Store<T extends UnknownRecord | UnknownArray> = T extends UnknownRecord ? RecordStore<T> : T extends UnknownArray ? ArrayStore<T> : never;
|
|
58
44
|
declare const TYPE_STORE = "Store";
|
|
59
45
|
/**
|
|
60
46
|
* Create a new store with deeply nested reactive properties
|
|
@@ -68,7 +54,7 @@ declare const TYPE_STORE = "Store";
|
|
|
68
54
|
* @param {T} initialValue - initial object or array value of the store
|
|
69
55
|
* @returns {Store<T>} - new store with reactive properties that preserves the original type T
|
|
70
56
|
*/
|
|
71
|
-
declare const
|
|
57
|
+
declare const createStore: <T extends UnknownRecord | UnknownArray>(initialValue: T) => Store<T>;
|
|
72
58
|
/**
|
|
73
59
|
* Check if the provided value is a Store instance
|
|
74
60
|
*
|
|
@@ -77,4 +63,4 @@ declare const store: <T extends UnknownRecord | UnknownArray>(initialValue: T) =
|
|
|
77
63
|
* @returns {boolean} - true if the value is a Store instance, false otherwise
|
|
78
64
|
*/
|
|
79
65
|
declare const isStore: <T extends UnknownRecordOrArray>(value: unknown) => value is Store<T>;
|
|
80
|
-
export { TYPE_STORE, isStore,
|
|
66
|
+
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, };
|
package/types/src/util.d.ts
CHANGED
|
@@ -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,
|
|
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
|
-
}
|