@zeix/cause-effect 0.17.3 → 0.18.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 +163 -232
- package/.cursorrules +41 -35
- package/.github/copilot-instructions.md +166 -116
- package/ARCHITECTURE.md +274 -0
- package/CLAUDE.md +199 -143
- package/COLLECTION_REFACTORING.md +161 -0
- package/GUIDE.md +298 -0
- package/README.md +232 -197
- package/REQUIREMENTS.md +100 -0
- package/bench/reactivity.bench.ts +577 -0
- package/index.dev.js +1325 -997
- package/index.js +1 -1
- package/index.ts +58 -74
- package/package.json +4 -1
- package/src/errors.ts +118 -74
- package/src/graph.ts +601 -0
- package/src/nodes/collection.ts +474 -0
- package/src/nodes/effect.ts +149 -0
- package/src/nodes/list.ts +588 -0
- package/src/nodes/memo.ts +120 -0
- package/src/nodes/sensor.ts +139 -0
- package/src/nodes/state.ts +135 -0
- package/src/nodes/store.ts +383 -0
- package/src/nodes/task.ts +146 -0
- package/src/signal.ts +112 -66
- package/src/util.ts +26 -57
- package/test/batch.test.ts +96 -62
- package/test/benchmark.test.ts +473 -487
- package/test/collection.test.ts +466 -706
- package/test/effect.test.ts +293 -696
- package/test/list.test.ts +335 -592
- package/test/memo.test.ts +380 -0
- package/test/regression.test.ts +156 -0
- package/test/scope.test.ts +191 -0
- package/test/sensor.test.ts +454 -0
- package/test/signal.test.ts +220 -213
- package/test/state.test.ts +217 -265
- package/test/store.test.ts +346 -446
- package/test/task.test.ts +395 -0
- package/test/untrack.test.ts +167 -0
- package/types/index.d.ts +13 -15
- package/types/src/errors.d.ts +73 -17
- package/types/src/graph.d.ts +208 -0
- package/types/src/nodes/collection.d.ts +64 -0
- package/types/src/nodes/effect.d.ts +48 -0
- package/types/src/nodes/list.d.ts +65 -0
- package/types/src/nodes/memo.d.ts +57 -0
- package/types/src/nodes/sensor.d.ts +75 -0
- package/types/src/nodes/state.d.ts +78 -0
- package/types/src/nodes/store.d.ts +51 -0
- package/types/src/nodes/task.d.ts +73 -0
- package/types/src/signal.d.ts +43 -29
- package/types/src/util.d.ts +9 -16
- package/archive/benchmark.ts +0 -683
- package/archive/collection.ts +0 -253
- package/archive/composite.ts +0 -85
- package/archive/computed.ts +0 -195
- package/archive/list.ts +0 -483
- package/archive/memo.ts +0 -139
- package/archive/state.ts +0 -90
- package/archive/store.ts +0 -298
- package/archive/task.ts +0 -189
- package/src/classes/collection.ts +0 -245
- package/src/classes/computed.ts +0 -349
- package/src/classes/list.ts +0 -343
- package/src/classes/ref.ts +0 -70
- package/src/classes/state.ts +0 -102
- package/src/classes/store.ts +0 -262
- package/src/diff.ts +0 -138
- package/src/effect.ts +0 -93
- package/src/match.ts +0 -45
- package/src/resolve.ts +0 -49
- package/src/system.ts +0 -257
- package/test/computed.test.ts +0 -1108
- package/test/diff.test.ts +0 -955
- package/test/match.test.ts +0 -388
- package/test/ref.test.ts +0 -353
- package/test/resolve.test.ts +0 -154
- package/types/src/classes/collection.d.ts +0 -45
- package/types/src/classes/computed.d.ts +0 -94
- package/types/src/classes/list.d.ts +0 -43
- package/types/src/classes/ref.d.ts +0 -35
- package/types/src/classes/state.d.ts +0 -49
- package/types/src/classes/store.d.ts +0 -52
- package/types/src/diff.d.ts +0 -28
- package/types/src/effect.d.ts +0 -15
- package/types/src/match.d.ts +0 -21
- package/types/src/resolve.d.ts +0 -29
- package/types/src/system.d.ts +0 -78
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { type Guard } from './errors';
|
|
2
|
+
type SourceFields<T extends {}> = {
|
|
3
|
+
value: T;
|
|
4
|
+
sinks: Edge | null;
|
|
5
|
+
sinksTail: Edge | null;
|
|
6
|
+
stop?: Cleanup;
|
|
7
|
+
};
|
|
8
|
+
type OptionsFields<T extends {}> = {
|
|
9
|
+
equals: (a: T, b: T) => boolean;
|
|
10
|
+
guard?: Guard<T>;
|
|
11
|
+
};
|
|
12
|
+
type SinkFields = {
|
|
13
|
+
fn: unknown;
|
|
14
|
+
flags: number;
|
|
15
|
+
sources: Edge | null;
|
|
16
|
+
sourcesTail: Edge | null;
|
|
17
|
+
};
|
|
18
|
+
type OwnerFields = {
|
|
19
|
+
cleanup: Cleanup | Cleanup[] | null;
|
|
20
|
+
};
|
|
21
|
+
type AsyncFields = {
|
|
22
|
+
controller: AbortController | undefined;
|
|
23
|
+
error: Error | undefined;
|
|
24
|
+
};
|
|
25
|
+
type StateNode<T extends {}> = SourceFields<T> & OptionsFields<T>;
|
|
26
|
+
type MemoNode<T extends {}> = SourceFields<T> & OptionsFields<T> & SinkFields & {
|
|
27
|
+
fn: MemoCallback<T>;
|
|
28
|
+
error: Error | undefined;
|
|
29
|
+
};
|
|
30
|
+
type TaskNode<T extends {}> = SourceFields<T> & OptionsFields<T> & SinkFields & AsyncFields & {
|
|
31
|
+
fn: (prev: T, abort: AbortSignal) => Promise<T>;
|
|
32
|
+
};
|
|
33
|
+
type EffectNode = SinkFields & OwnerFields & {
|
|
34
|
+
fn: EffectCallback;
|
|
35
|
+
};
|
|
36
|
+
type Scope = OwnerFields;
|
|
37
|
+
type SourceNode = SourceFields<unknown & {}>;
|
|
38
|
+
type SinkNode = MemoNode<unknown & {}> | TaskNode<unknown & {}> | EffectNode;
|
|
39
|
+
type OwnerNode = EffectNode | Scope;
|
|
40
|
+
type Edge = {
|
|
41
|
+
source: SourceNode;
|
|
42
|
+
sink: SinkNode;
|
|
43
|
+
nextSource: Edge | null;
|
|
44
|
+
prevSink: Edge | null;
|
|
45
|
+
nextSink: Edge | null;
|
|
46
|
+
};
|
|
47
|
+
type Signal<T extends {}> = {
|
|
48
|
+
get(): T;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A cleanup function that can be called to dispose of resources.
|
|
52
|
+
*/
|
|
53
|
+
type Cleanup = () => void;
|
|
54
|
+
type MaybeCleanup = Cleanup | undefined | void;
|
|
55
|
+
/**
|
|
56
|
+
* Options for configuring signal behavior.
|
|
57
|
+
*
|
|
58
|
+
* @template T - The type of value in the signal
|
|
59
|
+
*/
|
|
60
|
+
type SignalOptions<T extends {}> = {
|
|
61
|
+
/**
|
|
62
|
+
* Optional type guard to validate values.
|
|
63
|
+
* If provided, will throw an error if an invalid value is set.
|
|
64
|
+
*/
|
|
65
|
+
guard?: Guard<T>;
|
|
66
|
+
/**
|
|
67
|
+
* Optional custom equality function.
|
|
68
|
+
* Used to determine if a new value is different from the old value.
|
|
69
|
+
* Defaults to reference equality (===).
|
|
70
|
+
*/
|
|
71
|
+
equals?: (a: T, b: T) => boolean;
|
|
72
|
+
};
|
|
73
|
+
type ComputedOptions<T extends {}> = SignalOptions<T> & {
|
|
74
|
+
/**
|
|
75
|
+
* Optional initial value.
|
|
76
|
+
* Useful for reducer patterns so that calculations start with a value of correct type.
|
|
77
|
+
*/
|
|
78
|
+
value?: T;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* A callback function for memos that computes a value based on the previous value.
|
|
82
|
+
*
|
|
83
|
+
* @template T - The type of value computed
|
|
84
|
+
* @param prev - The previous computed value
|
|
85
|
+
* @returns The new computed value
|
|
86
|
+
*/
|
|
87
|
+
type MemoCallback<T extends {}> = (prev: T | undefined) => T;
|
|
88
|
+
/**
|
|
89
|
+
* A callback function for tasks that asynchronously computes a value.
|
|
90
|
+
*
|
|
91
|
+
* @template T - The type of value computed
|
|
92
|
+
* @param prev - The previous computed value
|
|
93
|
+
* @param signal - An AbortSignal that will be triggered if the task is aborted
|
|
94
|
+
* @returns A promise that resolves to the new computed value
|
|
95
|
+
*/
|
|
96
|
+
type TaskCallback<T extends {}> = (prev: T | undefined, signal: AbortSignal) => Promise<T>;
|
|
97
|
+
/**
|
|
98
|
+
* A callback function for effects that can perform side effects.
|
|
99
|
+
*
|
|
100
|
+
* @param match - A function to register cleanup callbacks that will be called before the effect re-runs or is disposed
|
|
101
|
+
*/
|
|
102
|
+
type EffectCallback = () => MaybeCleanup;
|
|
103
|
+
declare const TYPE_STATE = "State";
|
|
104
|
+
declare const TYPE_MEMO = "Memo";
|
|
105
|
+
declare const TYPE_TASK = "Task";
|
|
106
|
+
declare const TYPE_SENSOR = "Sensor";
|
|
107
|
+
declare const TYPE_LIST = "List";
|
|
108
|
+
declare const TYPE_COLLECTION = "Collection";
|
|
109
|
+
declare const TYPE_STORE = "Store";
|
|
110
|
+
declare const FLAG_CLEAN = 0;
|
|
111
|
+
declare const FLAG_DIRTY: number;
|
|
112
|
+
declare let activeSink: SinkNode | null;
|
|
113
|
+
declare let activeOwner: OwnerNode | null;
|
|
114
|
+
declare let batchDepth: number;
|
|
115
|
+
declare const DEFAULT_EQUALITY: <T extends {}>(a: T, b: T) => boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Equality function that always returns false, causing propagation on every update.
|
|
118
|
+
* Use with `createSensor` for observing mutable objects where the reference stays the same
|
|
119
|
+
* but internal state changes (e.g., DOM elements observed via MutationObserver).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* const el = createSensor<HTMLElement>((set) => {
|
|
124
|
+
* const node = document.getElementById('box')!;
|
|
125
|
+
* set(node);
|
|
126
|
+
* const obs = new MutationObserver(() => set(node));
|
|
127
|
+
* obs.observe(node, { attributes: true });
|
|
128
|
+
* return () => obs.disconnect();
|
|
129
|
+
* }, { value: node, equals: SKIP_EQUALITY });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare const SKIP_EQUALITY: (_a?: unknown, _b?: unknown) => boolean;
|
|
133
|
+
declare function link(source: SourceNode, sink: SinkNode): void;
|
|
134
|
+
declare function unlink(edge: Edge): Edge | null;
|
|
135
|
+
declare function trimSources(node: SinkNode): void;
|
|
136
|
+
declare function propagate(node: SinkNode, newFlag?: number): void;
|
|
137
|
+
declare function setState<T extends {}>(node: StateNode<T>, next: T): void;
|
|
138
|
+
declare function registerCleanup(owner: OwnerNode, fn: Cleanup): void;
|
|
139
|
+
declare function runCleanup(owner: OwnerNode): void;
|
|
140
|
+
declare function runEffect(node: EffectNode): void;
|
|
141
|
+
declare function refresh(node: SinkNode): void;
|
|
142
|
+
declare function flush(): void;
|
|
143
|
+
/**
|
|
144
|
+
* Batches multiple signal updates together.
|
|
145
|
+
* Effects will not run until the batch completes.
|
|
146
|
+
* Batches can be nested; effects run when the outermost batch completes.
|
|
147
|
+
*
|
|
148
|
+
* @param fn - The function to execute within the batch
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const count = createState(0);
|
|
153
|
+
* const double = createMemo(() => count.get() * 2);
|
|
154
|
+
*
|
|
155
|
+
* batch(() => {
|
|
156
|
+
* count.set(1);
|
|
157
|
+
* count.set(2);
|
|
158
|
+
* count.set(3);
|
|
159
|
+
* // Effects run only once at the end with count = 3
|
|
160
|
+
* });
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
declare function batch(fn: () => void): void;
|
|
164
|
+
/**
|
|
165
|
+
* Runs a callback without tracking dependencies.
|
|
166
|
+
* Any signal reads inside the callback will not create edges to the current active sink.
|
|
167
|
+
*
|
|
168
|
+
* @param fn - The function to execute without tracking
|
|
169
|
+
* @returns The return value of the function
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* const count = createState(0);
|
|
174
|
+
* const label = createState('Count');
|
|
175
|
+
*
|
|
176
|
+
* createEffect(() => {
|
|
177
|
+
* // Only re-runs when count changes, not when label changes
|
|
178
|
+
* const name = untrack(() => label.get());
|
|
179
|
+
* console.log(`${name}: ${count.get()}`);
|
|
180
|
+
* });
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
declare function untrack<T>(fn: () => T): T;
|
|
184
|
+
/**
|
|
185
|
+
* Creates a new ownership scope for managing cleanup of nested effects and resources.
|
|
186
|
+
* All effects created within the scope will be automatically disposed when the scope is disposed.
|
|
187
|
+
* Scopes can be nested - disposing a parent scope disposes all child scopes.
|
|
188
|
+
*
|
|
189
|
+
* @param fn - The function to execute within the scope, may return a cleanup function
|
|
190
|
+
* @returns A dispose function that cleans up the scope
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const dispose = createScope(() => {
|
|
195
|
+
* const count = createState(0);
|
|
196
|
+
*
|
|
197
|
+
* createEffect(() => {
|
|
198
|
+
* console.log(count.get());
|
|
199
|
+
* });
|
|
200
|
+
*
|
|
201
|
+
* return () => console.log('Scope disposed');
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* dispose(); // Cleans up the effect and runs cleanup callbacks
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
declare function createScope(fn: () => MaybeCleanup): Cleanup;
|
|
208
|
+
export { type Cleanup, type ComputedOptions, type EffectCallback, type EffectNode, type MaybeCleanup, type MemoCallback, type MemoNode, type Scope, type Signal, type SignalOptions, type SinkNode, type StateNode, type TaskCallback, type TaskNode, activeOwner, activeSink, batch, batchDepth, createScope, DEFAULT_EQUALITY as defaultEquals, SKIP_EQUALITY, FLAG_CLEAN, FLAG_DIRTY, flush, link, propagate, refresh, registerCleanup, runCleanup, runEffect, setState, trimSources, TYPE_COLLECTION, TYPE_LIST, TYPE_MEMO, TYPE_SENSOR, TYPE_STATE, TYPE_STORE, TYPE_TASK, unlink, untrack, };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Cleanup, type Signal } from '../graph';
|
|
2
|
+
import { type DiffResult, type KeyConfig, type List } from './list';
|
|
3
|
+
type CollectionSource<T extends {}> = List<T> | Collection<T>;
|
|
4
|
+
type DeriveCollectionCallback<T extends {}, U extends {}> = ((sourceValue: U) => T) | ((sourceValue: U, abort: AbortSignal) => Promise<T>);
|
|
5
|
+
type Collection<T extends {}> = {
|
|
6
|
+
readonly [Symbol.toStringTag]: 'Collection';
|
|
7
|
+
readonly [Symbol.isConcatSpreadable]: true;
|
|
8
|
+
[Symbol.iterator](): IterableIterator<Signal<T>>;
|
|
9
|
+
keys(): IterableIterator<string>;
|
|
10
|
+
get(): T[];
|
|
11
|
+
at(index: number): Signal<T> | undefined;
|
|
12
|
+
byKey(key: string): Signal<T> | undefined;
|
|
13
|
+
keyAt(index: number): string | undefined;
|
|
14
|
+
indexOfKey(key: string): number;
|
|
15
|
+
deriveCollection<R extends {}>(callback: (sourceValue: T) => R): Collection<R>;
|
|
16
|
+
deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): Collection<R>;
|
|
17
|
+
readonly length: number;
|
|
18
|
+
};
|
|
19
|
+
type CollectionOptions<T extends {}> = {
|
|
20
|
+
value?: T[];
|
|
21
|
+
keyConfig?: KeyConfig<T>;
|
|
22
|
+
createItem?: (key: string, value: T) => Signal<T>;
|
|
23
|
+
};
|
|
24
|
+
type CollectionCallback = (applyChanges: (changes: DiffResult) => void) => Cleanup;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a derived Collection from a List or another Collection with item-level memoization.
|
|
27
|
+
* Sync callbacks use createMemo, async callbacks use createTask.
|
|
28
|
+
* Structural changes are tracked reactively via the source's keys.
|
|
29
|
+
*
|
|
30
|
+
* @since 0.18.0
|
|
31
|
+
* @param source - The source List or Collection to derive from
|
|
32
|
+
* @param callback - Transformation function applied to each item
|
|
33
|
+
* @returns A Collection signal
|
|
34
|
+
*/
|
|
35
|
+
declare function deriveCollection<T extends {}, U extends {}>(source: CollectionSource<U>, callback: (sourceValue: U) => T): Collection<T>;
|
|
36
|
+
declare function deriveCollection<T extends {}, U extends {}>(source: CollectionSource<U>, callback: (sourceValue: U, abort: AbortSignal) => Promise<T>): Collection<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Creates an externally-driven Collection with a watched lifecycle.
|
|
39
|
+
* Items are managed by the start callback via `applyChanges(diffResult)`.
|
|
40
|
+
* The collection activates when first accessed by an effect and deactivates when no longer watched.
|
|
41
|
+
*
|
|
42
|
+
* @since 0.18.0
|
|
43
|
+
* @param start - Callback invoked when the collection starts being watched, receives applyChanges helper
|
|
44
|
+
* @param options - Optional configuration including initial value, key generation, and item signal creation
|
|
45
|
+
* @returns A read-only Collection signal
|
|
46
|
+
*/
|
|
47
|
+
declare function createCollection<T extends {}>(start: CollectionCallback, options?: CollectionOptions<T>): Collection<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a value is a Collection signal.
|
|
50
|
+
*
|
|
51
|
+
* @since 0.17.2
|
|
52
|
+
* @param value - The value to check
|
|
53
|
+
* @returns True if the value is a Collection
|
|
54
|
+
*/
|
|
55
|
+
declare function isCollection<T extends {}>(value: unknown): value is Collection<T>;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a value is a valid Collection source (List or Collection).
|
|
58
|
+
*
|
|
59
|
+
* @since 0.17.2
|
|
60
|
+
* @param value - The value to check
|
|
61
|
+
* @returns True if the value is a List or Collection
|
|
62
|
+
*/
|
|
63
|
+
declare function isCollectionSource<T extends {}>(value: unknown): value is CollectionSource<T>;
|
|
64
|
+
export { createCollection, deriveCollection, isCollection, isCollectionSource, type Collection, type CollectionCallback, type CollectionOptions, type CollectionSource, type DeriveCollectionCallback, };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Cleanup, type EffectCallback, type MaybeCleanup, type Signal } from '../graph';
|
|
2
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
3
|
+
type MatchHandlers<T extends Signal<unknown & {}>[]> = {
|
|
4
|
+
ok: (values: {
|
|
5
|
+
[K in keyof T]: T[K] extends Signal<infer V> ? V : never;
|
|
6
|
+
}) => MaybePromise<MaybeCleanup>;
|
|
7
|
+
err?: (errors: readonly Error[]) => MaybePromise<MaybeCleanup>;
|
|
8
|
+
nil?: () => MaybePromise<MaybeCleanup>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Creates a reactive effect that automatically runs when its dependencies change.
|
|
12
|
+
* Effects run immediately upon creation and re-run when any tracked signal changes.
|
|
13
|
+
* Effects are executed during the flush phase, after all updates have been batched.
|
|
14
|
+
*
|
|
15
|
+
* @since 0.1.0
|
|
16
|
+
* @param fn - The effect function that can track dependencies and register cleanup callbacks
|
|
17
|
+
* @returns A cleanup function that can be called to dispose of the effect
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const count = createState(0);
|
|
22
|
+
* const dispose = createEffect(() => {
|
|
23
|
+
* console.log('Count is:', count.get());
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* count.set(1); // Logs: "Count is: 1"
|
|
27
|
+
* dispose(); // Stop the effect
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* // With cleanup
|
|
33
|
+
* createEffect(() => {
|
|
34
|
+
* const timer = setInterval(() => console.log(count.get()), 1000);
|
|
35
|
+
* return () => clearInterval(timer);
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function createEffect(fn: EffectCallback): Cleanup;
|
|
40
|
+
/**
|
|
41
|
+
* Runs handlers based on the current values of signals.
|
|
42
|
+
* Must be called within an active owner (effect or scope) so async cleanup can be registered.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.15.0
|
|
45
|
+
* @throws RequiredOwnerError If called without an active owner.
|
|
46
|
+
*/
|
|
47
|
+
declare function match<T extends Signal<unknown & {}>[]>(signals: T, handlers: MatchHandlers<T>): MaybeCleanup;
|
|
48
|
+
export { type MaybePromise, type MatchHandlers, createEffect, match };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Cleanup, TYPE_LIST } from '../graph';
|
|
2
|
+
import { type Collection } from './collection';
|
|
3
|
+
import { type State } from './state';
|
|
4
|
+
type UnknownRecord = Record<string, unknown>;
|
|
5
|
+
type DiffResult = {
|
|
6
|
+
changed: boolean;
|
|
7
|
+
add: UnknownRecord;
|
|
8
|
+
change: UnknownRecord;
|
|
9
|
+
remove: UnknownRecord;
|
|
10
|
+
};
|
|
11
|
+
type KeyConfig<T> = string | ((item: T) => string);
|
|
12
|
+
type ListOptions<T extends {}> = {
|
|
13
|
+
keyConfig?: KeyConfig<T>;
|
|
14
|
+
watched?: () => Cleanup;
|
|
15
|
+
};
|
|
16
|
+
type List<T extends {}> = {
|
|
17
|
+
readonly [Symbol.toStringTag]: 'List';
|
|
18
|
+
readonly [Symbol.isConcatSpreadable]: true;
|
|
19
|
+
[Symbol.iterator](): IterableIterator<State<T>>;
|
|
20
|
+
readonly length: number;
|
|
21
|
+
get(): T[];
|
|
22
|
+
set(newValue: T[]): void;
|
|
23
|
+
update(fn: (oldValue: T[]) => T[]): void;
|
|
24
|
+
at(index: number): State<T> | undefined;
|
|
25
|
+
keys(): IterableIterator<string>;
|
|
26
|
+
byKey(key: string): State<T> | undefined;
|
|
27
|
+
keyAt(index: number): string | undefined;
|
|
28
|
+
indexOfKey(key: string): number;
|
|
29
|
+
add(value: T): string;
|
|
30
|
+
remove(keyOrIndex: string | number): void;
|
|
31
|
+
sort(compareFn?: (a: T, b: T) => number): void;
|
|
32
|
+
splice(start: number, deleteCount?: number, ...items: T[]): T[];
|
|
33
|
+
deriveCollection<R extends {}>(callback: (sourceValue: T) => R): Collection<R>;
|
|
34
|
+
deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): Collection<R>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Checks if two values are equal with cycle detection
|
|
38
|
+
*
|
|
39
|
+
* @since 0.15.0
|
|
40
|
+
* @param {T} a - First value to compare
|
|
41
|
+
* @param {T} b - Second value to compare
|
|
42
|
+
* @param {WeakSet<object>} visited - Set to track visited objects for cycle detection
|
|
43
|
+
* @returns {boolean} Whether the two values are equal
|
|
44
|
+
*/
|
|
45
|
+
/** Shallow equality check for string arrays */
|
|
46
|
+
declare function keysEqual(a: string[], b: string[]): boolean;
|
|
47
|
+
declare function isEqual<T>(a: T, b: T, visited?: WeakSet<object>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a reactive list with stable keys and per-item reactivity.
|
|
50
|
+
*
|
|
51
|
+
* @since 0.18.0
|
|
52
|
+
* @param initialValue - Initial array of items
|
|
53
|
+
* @param options - Optional configuration for key generation and watch lifecycle
|
|
54
|
+
* @returns A List signal
|
|
55
|
+
*/
|
|
56
|
+
declare function createList<T extends {}>(initialValue: T[], options?: ListOptions<T>): List<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a value is a List signal.
|
|
59
|
+
*
|
|
60
|
+
* @since 0.15.0
|
|
61
|
+
* @param value - The value to check
|
|
62
|
+
* @returns True if the value is a List
|
|
63
|
+
*/
|
|
64
|
+
declare function isList<T extends {}>(value: unknown): value is List<T>;
|
|
65
|
+
export { type DiffResult, type KeyConfig, type List, type ListOptions, type UnknownRecord, createList, isEqual, isList, keysEqual, TYPE_LIST, };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ComputedOptions, type MemoCallback } from '../graph';
|
|
2
|
+
/**
|
|
3
|
+
* A derived reactive computation that caches its result.
|
|
4
|
+
* Automatically tracks dependencies and recomputes when they change.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The type of value computed by the memo
|
|
7
|
+
*/
|
|
8
|
+
type Memo<T extends {}> = {
|
|
9
|
+
readonly [Symbol.toStringTag]: 'Memo';
|
|
10
|
+
/**
|
|
11
|
+
* Gets the current value of the memo.
|
|
12
|
+
* Recomputes if dependencies have changed since last access.
|
|
13
|
+
* When called inside another reactive context, creates a dependency.
|
|
14
|
+
* @returns The computed value
|
|
15
|
+
* @throws UnsetSignalValueError If the memo value is still unset when read.
|
|
16
|
+
*/
|
|
17
|
+
get(): T;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Creates a derived reactive computation that caches its result.
|
|
21
|
+
* The computation automatically tracks dependencies and recomputes when they change.
|
|
22
|
+
* Uses lazy evaluation - only computes when the value is accessed.
|
|
23
|
+
*
|
|
24
|
+
* @since 0.18.0
|
|
25
|
+
* @template T - The type of value computed by the memo
|
|
26
|
+
* @param fn - The computation function that receives the previous value
|
|
27
|
+
* @param options - Optional configuration for the memo
|
|
28
|
+
* @returns A Memo object with a get() method
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const count = createState(0);
|
|
33
|
+
* const doubled = createMemo(() => count.get() * 2);
|
|
34
|
+
* console.log(doubled.get()); // 0
|
|
35
|
+
* count.set(5);
|
|
36
|
+
* console.log(doubled.get()); // 10
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // Using previous value
|
|
42
|
+
* const sum = createMemo((prev) => prev + count.get(), { value: 0, equals: Object.is });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function createMemo<T extends {}>(fn: (prev: T) => T, options: ComputedOptions<T> & {
|
|
46
|
+
value: T;
|
|
47
|
+
}): Memo<T>;
|
|
48
|
+
declare function createMemo<T extends {}>(fn: MemoCallback<T>, options?: ComputedOptions<T>): Memo<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Checks if a value is a Memo signal.
|
|
51
|
+
*
|
|
52
|
+
* @since 0.18.0
|
|
53
|
+
* @param value - The value to check
|
|
54
|
+
* @returns True if the value is a Memo
|
|
55
|
+
*/
|
|
56
|
+
declare function isMemo<T extends {} = unknown & {}>(value: unknown): value is Memo<T>;
|
|
57
|
+
export { createMemo, isMemo, type Memo };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type Cleanup, type ComputedOptions } from '../graph';
|
|
2
|
+
/**
|
|
3
|
+
* A read-only signal that tracks external input and updates a state value as long as it is active.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of value produced by the sensor
|
|
6
|
+
*/
|
|
7
|
+
type Sensor<T extends {}> = {
|
|
8
|
+
readonly [Symbol.toStringTag]: 'Sensor';
|
|
9
|
+
/**
|
|
10
|
+
* Gets the current value of the sensor.
|
|
11
|
+
* Updates its state value if the sensor is active.
|
|
12
|
+
* When called inside another reactive context, creates a dependency.
|
|
13
|
+
* @returns The sensor value
|
|
14
|
+
* @throws UnsetSignalValueError If the sensor value is still unset when read.
|
|
15
|
+
*/
|
|
16
|
+
get(): T;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A callback function for sensors when the sensor starts being watched.
|
|
20
|
+
*
|
|
21
|
+
* @template T - The type of value observed
|
|
22
|
+
* @param set - A function to set the observed value
|
|
23
|
+
* @returns A cleanup function when the sensor stops being watched
|
|
24
|
+
*/
|
|
25
|
+
type SensorCallback<T extends {}> = (set: (next: T) => void) => Cleanup;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a sensor that tracks external input and updates a state value as long as it is active.
|
|
28
|
+
* Sensors get activated when they are first accessed by an effect and deactivated when they are
|
|
29
|
+
* no longer watched. This lazy activation pattern ensures resources are only consumed when needed.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.18.0
|
|
32
|
+
* @template T - The type of value stored in the state
|
|
33
|
+
* @param start - The callback function that starts the sensor and returns a cleanup function.
|
|
34
|
+
* @param options - Optional options for the sensor.
|
|
35
|
+
* @param options.value - Optional initial value. Avoids `UnsetSignalValueError` on first read
|
|
36
|
+
* before the start callback fires. Essential for the mutable-object observation pattern.
|
|
37
|
+
* @param options.equals - Optional equality function. Defaults to `Object.is`. Use `SKIP_EQUALITY`
|
|
38
|
+
* for mutable objects where the reference stays the same but internal state changes.
|
|
39
|
+
* @param options.guard - Optional type guard to validate values.
|
|
40
|
+
* @returns A read-only sensor signal.
|
|
41
|
+
*
|
|
42
|
+
* @example Tracking external values
|
|
43
|
+
* ```ts
|
|
44
|
+
* const mousePos = createSensor<{ x: number; y: number }>((set) => {
|
|
45
|
+
* const handler = (e: MouseEvent) => {
|
|
46
|
+
* set({ x: e.clientX, y: e.clientY });
|
|
47
|
+
* };
|
|
48
|
+
* window.addEventListener('mousemove', handler);
|
|
49
|
+
* return () => window.removeEventListener('mousemove', handler);
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Observing a mutable object
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { createSensor, SKIP_EQUALITY } from 'cause-effect';
|
|
56
|
+
*
|
|
57
|
+
* const el = createSensor<HTMLElement>((set) => {
|
|
58
|
+
* const node = document.getElementById('box')!;
|
|
59
|
+
* set(node);
|
|
60
|
+
* const obs = new MutationObserver(() => set(node));
|
|
61
|
+
* obs.observe(node, { attributes: true });
|
|
62
|
+
* return () => obs.disconnect();
|
|
63
|
+
* }, { value: node, equals: SKIP_EQUALITY });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function createSensor<T extends {}>(start: SensorCallback<T>, options?: ComputedOptions<T>): Sensor<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a value is a Sensor signal.
|
|
69
|
+
*
|
|
70
|
+
* @since 0.18.0
|
|
71
|
+
* @param value - The value to check
|
|
72
|
+
* @returns True if the value is a Sensor
|
|
73
|
+
*/
|
|
74
|
+
declare function isSensor<T extends {} = unknown & {}>(value: unknown): value is Sensor<T>;
|
|
75
|
+
export { createSensor, isSensor, type Sensor, type SensorCallback };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type SignalOptions } from '../graph';
|
|
2
|
+
/**
|
|
3
|
+
* A callback function for states that updates a value based on the previous value.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of value
|
|
6
|
+
* @param prev - The previous state value
|
|
7
|
+
* @returns The new state value
|
|
8
|
+
*/
|
|
9
|
+
type UpdateCallback<T extends {}> = (prev: T) => T;
|
|
10
|
+
/**
|
|
11
|
+
* A mutable reactive state container.
|
|
12
|
+
* Changes to the state will automatically propagate to dependent computations and effects.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of value stored in the state
|
|
15
|
+
*/
|
|
16
|
+
type State<T extends {}> = {
|
|
17
|
+
readonly [Symbol.toStringTag]: 'State';
|
|
18
|
+
/**
|
|
19
|
+
* Gets the current value of the state.
|
|
20
|
+
* When called inside a memo, task, or effect, creates a dependency.
|
|
21
|
+
* @returns The current value
|
|
22
|
+
*/
|
|
23
|
+
get(): T;
|
|
24
|
+
/**
|
|
25
|
+
* Sets a new value for the state.
|
|
26
|
+
* If the new value is different (according to the equality function), all dependents will be notified.
|
|
27
|
+
* @param next - The new value to set
|
|
28
|
+
*/
|
|
29
|
+
set(next: T): void;
|
|
30
|
+
/**
|
|
31
|
+
* Updates the state with a new value computed by a callback function.
|
|
32
|
+
* The callback receives the current value as an argument.
|
|
33
|
+
* @param fn - The callback function to compute the new value
|
|
34
|
+
*/
|
|
35
|
+
update(fn: UpdateCallback<T>): void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Creates a mutable reactive state container.
|
|
39
|
+
*
|
|
40
|
+
* @since 0.9.0
|
|
41
|
+
* @template T - The type of value stored in the state
|
|
42
|
+
* @param value - The initial value
|
|
43
|
+
* @param options - Optional configuration for the state
|
|
44
|
+
* @returns A State object with get() and set() methods
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const count = createState(0);
|
|
49
|
+
* count.set(1);
|
|
50
|
+
* console.log(count.get()); // 1
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* // With type guard
|
|
56
|
+
* const count = createState(0, {
|
|
57
|
+
* guard: (v): v is number => typeof v === 'number'
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function createState<T extends {}>(value: T, options?: SignalOptions<T>): State<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Checks if a value is a State signal.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.9.0
|
|
66
|
+
* @param value - The value to check
|
|
67
|
+
* @returns True if the value is a State
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const state = createState(0);
|
|
72
|
+
* if (isState(state)) {
|
|
73
|
+
* state.set(1); // TypeScript knows state has set()
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
declare function isState<T extends {} = unknown & {}>(value: unknown): value is State<T>;
|
|
78
|
+
export { createState, isState, type State, type UpdateCallback };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type Cleanup, TYPE_STORE } from '../graph';
|
|
2
|
+
import { type List, type UnknownRecord } from './list';
|
|
3
|
+
import { type State } from './state';
|
|
4
|
+
type StoreOptions = {
|
|
5
|
+
watched?: () => Cleanup;
|
|
6
|
+
};
|
|
7
|
+
type BaseStore<T extends UnknownRecord> = {
|
|
8
|
+
readonly [Symbol.toStringTag]: 'Store';
|
|
9
|
+
readonly [Symbol.isConcatSpreadable]: false;
|
|
10
|
+
[Symbol.iterator](): IterableIterator<[
|
|
11
|
+
string,
|
|
12
|
+
State<T[keyof T] & {}> | Store<UnknownRecord> | List<unknown & {}>
|
|
13
|
+
]>;
|
|
14
|
+
keys(): IterableIterator<string>;
|
|
15
|
+
byKey<K extends keyof T & string>(key: K): T[K] extends readonly (infer U extends {})[] ? List<U> : T[K] extends UnknownRecord ? Store<T[K]> : T[K] extends unknown & {} ? State<T[K] & {}> : State<T[K] & {}> | undefined;
|
|
16
|
+
get(): T;
|
|
17
|
+
set(newValue: T): void;
|
|
18
|
+
update(fn: (oldValue: T) => T): void;
|
|
19
|
+
add<K extends keyof T & string>(key: K, value: T[K]): K;
|
|
20
|
+
remove(key: string): void;
|
|
21
|
+
};
|
|
22
|
+
type Store<T extends UnknownRecord> = BaseStore<T> & {
|
|
23
|
+
[K in keyof T]: T[K] extends readonly (infer U extends {})[] ? List<U> : T[K] extends UnknownRecord ? Store<T[K]> : T[K] extends unknown & {} ? State<T[K] & {}> : State<T[K] & {}> | undefined;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Creates a reactive store with deeply nested reactive properties.
|
|
27
|
+
* Each property becomes its own signal (State for primitives, nested Store for objects, List for arrays).
|
|
28
|
+
* Properties are accessible directly via proxy.
|
|
29
|
+
*
|
|
30
|
+
* @since 0.15.0
|
|
31
|
+
* @param initialValue - Initial object value of the store
|
|
32
|
+
* @param options - Optional configuration for watch lifecycle
|
|
33
|
+
* @returns A Store with reactive properties
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const user = createStore({ name: 'Alice', age: 30 });
|
|
38
|
+
* user.name.set('Bob'); // Only name subscribers react
|
|
39
|
+
* console.log(user.get()); // { name: 'Bob', age: 30 }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createStore<T extends UnknownRecord>(initialValue: T, options?: StoreOptions): Store<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if a value is a Store signal.
|
|
45
|
+
*
|
|
46
|
+
* @since 0.15.0
|
|
47
|
+
* @param value - The value to check
|
|
48
|
+
* @returns True if the value is a Store
|
|
49
|
+
*/
|
|
50
|
+
declare function isStore<T extends UnknownRecord>(value: unknown): value is Store<T>;
|
|
51
|
+
export { createStore, isStore, type Store, type StoreOptions, TYPE_STORE };
|