@zeix/cause-effect 0.17.2 → 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.
Files changed (94) hide show
  1. package/.ai-context.md +163 -226
  2. package/.cursorrules +41 -35
  3. package/.github/copilot-instructions.md +166 -116
  4. package/.zed/settings.json +3 -0
  5. package/ARCHITECTURE.md +274 -0
  6. package/CLAUDE.md +197 -202
  7. package/COLLECTION_REFACTORING.md +161 -0
  8. package/GUIDE.md +298 -0
  9. package/README.md +241 -220
  10. package/REQUIREMENTS.md +100 -0
  11. package/bench/reactivity.bench.ts +577 -0
  12. package/index.dev.js +1326 -1174
  13. package/index.js +1 -1
  14. package/index.ts +58 -85
  15. package/package.json +9 -6
  16. package/src/errors.ts +118 -70
  17. package/src/graph.ts +601 -0
  18. package/src/nodes/collection.ts +474 -0
  19. package/src/nodes/effect.ts +149 -0
  20. package/src/nodes/list.ts +588 -0
  21. package/src/nodes/memo.ts +120 -0
  22. package/src/nodes/sensor.ts +139 -0
  23. package/src/nodes/state.ts +135 -0
  24. package/src/nodes/store.ts +383 -0
  25. package/src/nodes/task.ts +146 -0
  26. package/src/signal.ts +112 -64
  27. package/src/util.ts +26 -57
  28. package/test/batch.test.ts +96 -69
  29. package/test/benchmark.test.ts +473 -485
  30. package/test/collection.test.ts +455 -955
  31. package/test/effect.test.ts +293 -696
  32. package/test/list.test.ts +332 -857
  33. package/test/memo.test.ts +380 -0
  34. package/test/regression.test.ts +156 -0
  35. package/test/scope.test.ts +191 -0
  36. package/test/sensor.test.ts +454 -0
  37. package/test/signal.test.ts +220 -213
  38. package/test/state.test.ts +217 -271
  39. package/test/store.test.ts +346 -898
  40. package/test/task.test.ts +395 -0
  41. package/test/untrack.test.ts +167 -0
  42. package/test/util/dependency-graph.ts +2 -2
  43. package/tsconfig.build.json +11 -0
  44. package/tsconfig.json +5 -7
  45. package/types/index.d.ts +13 -15
  46. package/types/src/errors.d.ts +73 -19
  47. package/types/src/graph.d.ts +208 -0
  48. package/types/src/nodes/collection.d.ts +64 -0
  49. package/types/src/nodes/effect.d.ts +48 -0
  50. package/types/src/nodes/list.d.ts +65 -0
  51. package/types/src/nodes/memo.d.ts +57 -0
  52. package/types/src/nodes/sensor.d.ts +75 -0
  53. package/types/src/nodes/state.d.ts +78 -0
  54. package/types/src/nodes/store.d.ts +51 -0
  55. package/types/src/nodes/task.d.ts +73 -0
  56. package/types/src/signal.d.ts +43 -28
  57. package/types/src/util.d.ts +9 -16
  58. package/archive/benchmark.ts +0 -688
  59. package/archive/collection.ts +0 -310
  60. package/archive/computed.ts +0 -198
  61. package/archive/list.ts +0 -544
  62. package/archive/memo.ts +0 -140
  63. package/archive/state.ts +0 -90
  64. package/archive/store.ts +0 -357
  65. package/archive/task.ts +0 -191
  66. package/src/classes/collection.ts +0 -298
  67. package/src/classes/composite.ts +0 -171
  68. package/src/classes/computed.ts +0 -392
  69. package/src/classes/list.ts +0 -310
  70. package/src/classes/ref.ts +0 -96
  71. package/src/classes/state.ts +0 -131
  72. package/src/classes/store.ts +0 -227
  73. package/src/diff.ts +0 -138
  74. package/src/effect.ts +0 -96
  75. package/src/match.ts +0 -45
  76. package/src/resolve.ts +0 -49
  77. package/src/system.ts +0 -275
  78. package/test/computed.test.ts +0 -1126
  79. package/test/diff.test.ts +0 -955
  80. package/test/match.test.ts +0 -388
  81. package/test/ref.test.ts +0 -381
  82. package/test/resolve.test.ts +0 -154
  83. package/types/src/classes/collection.d.ts +0 -47
  84. package/types/src/classes/composite.d.ts +0 -15
  85. package/types/src/classes/computed.d.ts +0 -114
  86. package/types/src/classes/list.d.ts +0 -41
  87. package/types/src/classes/ref.d.ts +0 -48
  88. package/types/src/classes/state.d.ts +0 -61
  89. package/types/src/classes/store.d.ts +0 -51
  90. package/types/src/diff.d.ts +0 -28
  91. package/types/src/effect.d.ts +0 -15
  92. package/types/src/match.d.ts +0 -21
  93. package/types/src/resolve.d.ts +0 -29
  94. package/types/src/system.d.ts +0 -81
@@ -1,31 +1,85 @@
1
- import { type MutableSignal } from './signal';
2
- type Guard<T> = (value: unknown) => value is T;
1
+ /**
2
+ * A type guard function that validates whether an unknown value is of type T.
3
+ * Used to ensure type safety when updating signals.
4
+ *
5
+ * @template T - The type to guard against
6
+ * @param value - The value to check
7
+ * @returns True if the value is of type T
8
+ */
9
+ type Guard<T extends {}> = (value: unknown) => value is T;
10
+ /**
11
+ * Error thrown on re-entrance on an already running function.
12
+ */
3
13
  declare class CircularDependencyError extends Error {
14
+ /**
15
+ * Constructs a new CircularDependencyError.
16
+ *
17
+ * @param where - The location where the error occurred.
18
+ */
4
19
  constructor(where: string);
5
20
  }
6
- declare class DuplicateKeyError extends Error {
7
- constructor(where: string, key: string, value?: unknown);
21
+ /**
22
+ * Error thrown when a signal value is null or undefined.
23
+ */
24
+ declare class NullishSignalValueError extends TypeError {
25
+ /**
26
+ * Constructs a new NullishSignalValueError.
27
+ *
28
+ * @param where - The location where the error occurred.
29
+ */
30
+ constructor(where: string);
8
31
  }
9
- declare class InvalidCallbackError extends TypeError {
10
- constructor(where: string, value: unknown);
32
+ /**
33
+ * Error thrown when a signal is read before it has a value.
34
+ */
35
+ declare class UnsetSignalValueError extends Error {
36
+ /**
37
+ * Constructs a new UnsetSignalValueError.
38
+ *
39
+ * @param where - The location where the error occurred.
40
+ */
41
+ constructor(where: string);
11
42
  }
12
- declare class InvalidCollectionSourceError extends TypeError {
43
+ /**
44
+ * Error thrown when a signal value is invalid.
45
+ */
46
+ declare class InvalidSignalValueError extends TypeError {
47
+ /**
48
+ * Constructs a new InvalidSignalValueError.
49
+ *
50
+ * @param where - The location where the error occurred.
51
+ * @param value - The invalid value.
52
+ */
13
53
  constructor(where: string, value: unknown);
14
54
  }
15
- declare class InvalidHookError extends TypeError {
16
- constructor(where: string, type: string);
17
- }
18
- declare class InvalidSignalValueError extends TypeError {
55
+ /**
56
+ * Error thrown when a callback is invalid.
57
+ */
58
+ declare class InvalidCallbackError extends TypeError {
59
+ /**
60
+ * Constructs a new InvalidCallbackError.
61
+ *
62
+ * @param where - The location where the error occurred.
63
+ * @param value - The invalid value.
64
+ */
19
65
  constructor(where: string, value: unknown);
20
66
  }
21
- declare class NullishSignalValueError extends TypeError {
67
+ /**
68
+ * Error thrown when an API requiring an owner is called without one.
69
+ */
70
+ declare class RequiredOwnerError extends Error {
71
+ /**
72
+ * Constructs a new RequiredOwnerError.
73
+ *
74
+ * @param where - The location where the error occurred.
75
+ */
22
76
  constructor(where: string);
23
77
  }
24
- declare class ReadonlySignalError extends Error {
25
- constructor(what: string, value: unknown);
78
+ declare class DuplicateKeyError extends Error {
79
+ constructor(where: string, key: string, value?: unknown);
26
80
  }
27
- declare const createError: (reason: unknown) => Error;
28
- declare const validateCallback: (where: string, value: unknown, guard?: (value: unknown) => boolean) => void;
29
- declare const validateSignalValue: (where: string, value: unknown, guard?: (value: unknown) => boolean) => void;
30
- declare const guardMutableSignal: <T extends {}>(what: string, value: unknown, signal: unknown) => signal is MutableSignal<T>;
31
- export { type Guard, CircularDependencyError, DuplicateKeyError, InvalidCallbackError, InvalidCollectionSourceError, InvalidHookError, InvalidSignalValueError, NullishSignalValueError, ReadonlySignalError, createError, validateCallback, validateSignalValue, guardMutableSignal, };
81
+ declare function validateSignalValue<T extends {}>(where: string, value: unknown, guard?: Guard<T>): asserts value is T;
82
+ declare function validateReadValue<T extends {}>(where: string, value: T | null | undefined): asserts value is T;
83
+ declare function validateCallback(where: string, value: unknown): asserts value is (...args: unknown[]) => unknown;
84
+ declare function validateCallback<T>(where: string, value: unknown, guard: (value: unknown) => value is T): asserts value is T;
85
+ export { type Guard, CircularDependencyError, NullishSignalValueError, InvalidSignalValueError, UnsetSignalValueError, InvalidCallbackError, RequiredOwnerError, DuplicateKeyError, validateSignalValue, validateReadValue, validateCallback, };
@@ -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 };