@zeix/cause-effect 0.17.0 → 0.17.2

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 (50) hide show
  1. package/.ai-context.md +26 -5
  2. package/.cursorrules +8 -3
  3. package/.github/copilot-instructions.md +13 -4
  4. package/CLAUDE.md +191 -262
  5. package/README.md +268 -420
  6. package/archive/collection.ts +23 -25
  7. package/archive/computed.ts +5 -4
  8. package/archive/list.ts +21 -28
  9. package/archive/memo.ts +4 -2
  10. package/archive/state.ts +2 -1
  11. package/archive/store.ts +21 -32
  12. package/archive/task.ts +6 -9
  13. package/index.dev.js +411 -220
  14. package/index.js +1 -1
  15. package/index.ts +25 -8
  16. package/package.json +1 -1
  17. package/src/classes/collection.ts +103 -77
  18. package/src/classes/composite.ts +28 -33
  19. package/src/classes/computed.ts +90 -31
  20. package/src/classes/list.ts +39 -33
  21. package/src/classes/ref.ts +96 -0
  22. package/src/classes/state.ts +41 -8
  23. package/src/classes/store.ts +47 -30
  24. package/src/diff.ts +2 -1
  25. package/src/effect.ts +19 -9
  26. package/src/errors.ts +31 -1
  27. package/src/match.ts +5 -12
  28. package/src/resolve.ts +3 -2
  29. package/src/signal.ts +0 -1
  30. package/src/system.ts +159 -43
  31. package/src/util.ts +0 -10
  32. package/test/collection.test.ts +383 -67
  33. package/test/computed.test.ts +268 -11
  34. package/test/effect.test.ts +2 -2
  35. package/test/list.test.ts +249 -21
  36. package/test/ref.test.ts +381 -0
  37. package/test/state.test.ts +13 -13
  38. package/test/store.test.ts +473 -28
  39. package/types/index.d.ts +6 -5
  40. package/types/src/classes/collection.d.ts +27 -12
  41. package/types/src/classes/composite.d.ts +4 -4
  42. package/types/src/classes/computed.d.ts +17 -0
  43. package/types/src/classes/list.d.ts +6 -6
  44. package/types/src/classes/ref.d.ts +48 -0
  45. package/types/src/classes/state.d.ts +9 -0
  46. package/types/src/classes/store.d.ts +4 -4
  47. package/types/src/effect.d.ts +1 -2
  48. package/types/src/errors.d.ts +9 -1
  49. package/types/src/system.d.ts +40 -24
  50. package/types/src/util.d.ts +1 -3
@@ -1,3 +1,4 @@
1
+ import { type Cleanup, type HookCallback, type WatchHook } from '../system';
1
2
  type Computed<T extends {}> = {
2
3
  readonly [Symbol.toStringTag]: 'Computed';
3
4
  get(): T;
@@ -34,6 +35,14 @@ declare class Memo<T extends {}> {
34
35
  * @throws {Error} If an error occurs during computation
35
36
  */
36
37
  get(): T;
38
+ /**
39
+ * Register a callback to be called when HOOK_WATCH is triggered.
40
+ *
41
+ * @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
42
+ * @param {HookCallback} callback - The callback to register
43
+ * @returns {Cleanup} - A function to unregister the callback
44
+ */
45
+ on(type: WatchHook, callback: HookCallback): Cleanup;
37
46
  }
38
47
  /**
39
48
  * Create a new task signals that memoizes the result of an asynchronous function.
@@ -60,6 +69,14 @@ declare class Task<T extends {}> {
60
69
  * @throws {Error} If an error occurs during computation
61
70
  */
62
71
  get(): T;
72
+ /**
73
+ * Register a callback to be called when HOOK_WATCH is triggered.
74
+ *
75
+ * @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
76
+ * @param {HookCallback} callback - The callback to register
77
+ * @returns {Cleanup} - A function to unregister the callback
78
+ */
79
+ on(type: WatchHook, callback: HookCallback): Cleanup;
63
80
  }
64
81
  /**
65
82
  * Create a derived signal from existing signals
@@ -1,6 +1,6 @@
1
1
  import { type UnknownArray } from '../diff';
2
- import { type Cleanup, type Listener, type Notifications } from '../system';
3
- import { Collection } from './collection';
2
+ import { type Cleanup, type Hook, type HookCallback } from '../system';
3
+ import { DerivedCollection } from './collection';
4
4
  import { State } from './state';
5
5
  type ArrayToRecord<T extends UnknownArray> = {
6
6
  [key: string]: T extends Array<infer U extends {}> ? U : never;
@@ -11,7 +11,7 @@ declare class List<T extends {}> {
11
11
  #private;
12
12
  constructor(initialValue: T[], keyConfig?: KeyConfig<T>);
13
13
  get [Symbol.toStringTag](): 'List';
14
- get [Symbol.isConcatSpreadable](): boolean;
14
+ get [Symbol.isConcatSpreadable](): true;
15
15
  [Symbol.iterator](): IterableIterator<State<T>>;
16
16
  get length(): number;
17
17
  get(): T[];
@@ -26,9 +26,9 @@ declare class List<T extends {}> {
26
26
  remove(keyOrIndex: string | number): void;
27
27
  sort(compareFn?: (a: T, b: T) => number): void;
28
28
  splice(start: number, deleteCount?: number, ...items: T[]): T[];
29
- on<K extends keyof Notifications>(type: K, listener: Listener<K>): Cleanup;
30
- deriveCollection<R extends {}>(callback: (sourceValue: T) => R): Collection<R, T>;
31
- deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): Collection<R, T>;
29
+ on(type: Hook, callback: HookCallback): Cleanup;
30
+ deriveCollection<R extends {}>(callback: (sourceValue: T) => R): DerivedCollection<R, T>;
31
+ deriveCollection<R extends {}>(callback: (sourceValue: T, abort: AbortSignal) => Promise<R>): DerivedCollection<R, T>;
32
32
  }
33
33
  /**
34
34
  * Check if the provided value is a List instance
@@ -0,0 +1,48 @@
1
+ import { type Guard } from '../errors';
2
+ import { type Cleanup, type HookCallback, type WatchHook } from '../system';
3
+ declare const TYPE_REF = "Ref";
4
+ /**
5
+ * Create a new ref signal.
6
+ *
7
+ * @since 0.17.1
8
+ */
9
+ declare class Ref<T extends {}> {
10
+ #private;
11
+ /**
12
+ * Create a new ref signal.
13
+ *
14
+ * @param {T} value - Reference to external object
15
+ * @param {Guard<T>} guard - Optional guard function to validate the value
16
+ * @throws {NullishSignalValueError} - If the value is null or undefined
17
+ * @throws {InvalidSignalValueError} - If the value is invalid
18
+ */
19
+ constructor(value: T, guard?: Guard<T>);
20
+ get [Symbol.toStringTag](): string;
21
+ /**
22
+ * Get the value of the ref signal.
23
+ *
24
+ * @returns {T} - Object reference
25
+ */
26
+ get(): T;
27
+ /**
28
+ * Notify watchers of relevant changes in the external reference.
29
+ */
30
+ notify(): void;
31
+ /**
32
+ * Register a callback to be called when HOOK_WATCH is triggered.
33
+ *
34
+ * @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
35
+ * @param {HookCallback} callback - The callback to register
36
+ * @returns {Cleanup} - A function to unregister the callback
37
+ */
38
+ on(type: WatchHook, callback: HookCallback): Cleanup;
39
+ }
40
+ /**
41
+ * Check if the provided value is a Ref instance
42
+ *
43
+ * @since 0.17.1
44
+ * @param {unknown} value - Value to check
45
+ * @returns {boolean} - True if the value is a Ref instance, false otherwise
46
+ */
47
+ declare const isRef: <T extends {}>(value: unknown) => value is Ref<T>;
48
+ export { TYPE_REF, Ref, isRef };
@@ -1,3 +1,4 @@
1
+ import { type Cleanup, type HookCallback, type WatchHook } from '../system';
1
2
  declare const TYPE_STATE: "State";
2
3
  /**
3
4
  * Create a new state signal.
@@ -40,6 +41,14 @@ declare class State<T extends {}> {
40
41
  * @throws {InvalidSignalValueError} - If the initial value is invalid
41
42
  */
42
43
  update(updater: (oldValue: T) => T): void;
44
+ /**
45
+ * Register a callback to be called when HOOK_WATCH is triggered.
46
+ *
47
+ * @param {WatchHook} type - The type of hook to register the callback for; only HOOK_WATCH is supported
48
+ * @param {HookCallback} callback - The callback to register
49
+ * @returns {Cleanup} - A function to unregister the callback
50
+ */
51
+ on(type: WatchHook, callback: HookCallback): Cleanup;
43
52
  }
44
53
  /**
45
54
  * Check if the provided value is a State instance
@@ -1,6 +1,6 @@
1
1
  import { type UnknownRecord } from '../diff';
2
2
  import { type MutableSignal } from '../signal';
3
- import { type Cleanup, type Listener, type Listeners } from '../system';
3
+ import { type Cleanup, type Hook, type HookCallback } from '../system';
4
4
  import type { List } from './list';
5
5
  import type { State } from './state';
6
6
  type Store<T extends UnknownRecord> = BaseStore<T> & {
@@ -23,14 +23,14 @@ declare class BaseStore<T extends UnknownRecord> {
23
23
  string,
24
24
  MutableSignal<T[keyof T] & {}>
25
25
  ]>;
26
- get(): T;
27
- set(newValue: T): void;
28
26
  keys(): IterableIterator<string>;
29
27
  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;
28
+ get(): T;
29
+ set(newValue: T): void;
30
30
  update(fn: (oldValue: T) => T): void;
31
31
  add<K extends keyof T & string>(key: K, value: T[K]): K;
32
32
  remove(key: string): void;
33
- on<K extends keyof Omit<Listeners, 'sort'>>(type: K, listener: Listener<K>): Cleanup;
33
+ on(type: Hook, callback: HookCallback): Cleanup;
34
34
  }
35
35
  /**
36
36
  * Create a new store with deeply nested reactive properties
@@ -1,5 +1,4 @@
1
- import { type Cleanup } from './system';
2
- type MaybeCleanup = Cleanup | undefined | void;
1
+ import { type Cleanup, type MaybeCleanup } from './system';
3
2
  type EffectCallback = (() => MaybeCleanup) | ((abort: AbortSignal) => Promise<MaybeCleanup>);
4
3
  /**
5
4
  * Define what happens when a reactive state changes
@@ -1,4 +1,5 @@
1
1
  import { type MutableSignal } from './signal';
2
+ type Guard<T> = (value: unknown) => value is T;
2
3
  declare class CircularDependencyError extends Error {
3
4
  constructor(where: string);
4
5
  }
@@ -8,6 +9,12 @@ declare class DuplicateKeyError extends Error {
8
9
  declare class InvalidCallbackError extends TypeError {
9
10
  constructor(where: string, value: unknown);
10
11
  }
12
+ declare class InvalidCollectionSourceError extends TypeError {
13
+ constructor(where: string, value: unknown);
14
+ }
15
+ declare class InvalidHookError extends TypeError {
16
+ constructor(where: string, type: string);
17
+ }
11
18
  declare class InvalidSignalValueError extends TypeError {
12
19
  constructor(where: string, value: unknown);
13
20
  }
@@ -17,7 +24,8 @@ declare class NullishSignalValueError extends TypeError {
17
24
  declare class ReadonlySignalError extends Error {
18
25
  constructor(what: string, value: unknown);
19
26
  }
27
+ declare const createError: (reason: unknown) => Error;
20
28
  declare const validateCallback: (where: string, value: unknown, guard?: (value: unknown) => boolean) => void;
21
29
  declare const validateSignalValue: (where: string, value: unknown, guard?: (value: unknown) => boolean) => void;
22
30
  declare const guardMutableSignal: <T extends {}>(what: string, value: unknown, signal: unknown) => signal is MutableSignal<T>;
23
- export { CircularDependencyError, DuplicateKeyError, InvalidCallbackError, InvalidSignalValueError, NullishSignalValueError, ReadonlySignalError, validateCallback, validateSignalValue, guardMutableSignal, };
31
+ export { type Guard, CircularDependencyError, DuplicateKeyError, InvalidCallbackError, InvalidCollectionSourceError, InvalidHookError, InvalidSignalValueError, NullishSignalValueError, ReadonlySignalError, createError, validateCallback, validateSignalValue, guardMutableSignal, };
@@ -1,21 +1,26 @@
1
1
  type Cleanup = () => void;
2
+ type MaybeCleanup = Cleanup | undefined | void;
3
+ type Hook = 'add' | 'change' | 'cleanup' | 'remove' | 'sort' | 'watch';
4
+ type CleanupHook = 'cleanup';
5
+ type WatchHook = 'watch';
6
+ type HookCallback = (payload?: readonly string[]) => MaybeCleanup;
7
+ type HookCallbacks = {
8
+ [K in Hook]?: Set<HookCallback>;
9
+ };
2
10
  type Watcher = {
3
11
  (): void;
4
- onCleanup(cleanup: Cleanup): void;
12
+ on(type: CleanupHook, cleanup: Cleanup): void;
5
13
  stop(): void;
6
14
  };
7
- type Notifications = {
8
- add: readonly string[];
9
- change: readonly string[];
10
- remove: readonly string[];
11
- sort: readonly string[];
12
- };
13
- type Listener<K extends keyof Notifications> = (payload: Notifications[K]) => void;
14
- type Listeners = {
15
- [K in keyof Notifications]: Set<Listener<K>>;
16
- };
15
+ declare const UNSET: any;
16
+ declare const HOOK_ADD = "add";
17
+ declare const HOOK_CHANGE = "change";
18
+ declare const HOOK_CLEANUP = "cleanup";
19
+ declare const HOOK_REMOVE = "remove";
20
+ declare const HOOK_SORT = "sort";
21
+ declare const HOOK_WATCH = "watch";
17
22
  /**
18
- * Create a watcher that can be used to observe changes to a signal
23
+ * Create a watcher to observe changes to a signal.
19
24
  *
20
25
  * A watcher is a reaction function with onCleanup and stop methods
21
26
  *
@@ -25,29 +30,31 @@ type Listeners = {
25
30
  */
26
31
  declare const createWatcher: (react: () => void) => Watcher;
27
32
  /**
28
- * Subscribe by adding active watcher to the Set of watchers of a signal
33
+ * Subscribe by adding active watcher to the Set of watchers of a signal.
29
34
  *
30
35
  * @param {Set<Watcher>} watchers - Watchers of the signal
36
+ * @param {Set<HookCallback>} watchHookCallbacks - HOOK_WATCH callbacks of the signal
31
37
  */
32
- declare const subscribeActiveWatcher: (watchers: Set<Watcher>) => void;
38
+ declare const subscribeActiveWatcher: (watchers: Set<Watcher>, watchHookCallbacks?: Set<HookCallback>) => void;
33
39
  /**
34
- * Notify watchers of a signal change
40
+ * Notify watchers of a signal change.
35
41
  *
36
42
  * @param {Set<Watcher>} watchers - Watchers of the signal
43
+ * @returns {boolean} - Whether any watchers were notified
37
44
  */
38
- declare const notifyWatchers: (watchers: Set<Watcher>) => void;
45
+ declare const notifyWatchers: (watchers: Set<Watcher>) => boolean;
39
46
  /**
40
- * Flush all pending reactions of enqueued watchers
47
+ * Flush all pending reactions of enqueued watchers.
41
48
  */
42
49
  declare const flushPendingReactions: () => void;
43
50
  /**
44
- * Batch multiple signal writes
51
+ * Batch multiple signal writes.
45
52
  *
46
53
  * @param {() => void} callback - Function with multiple signal writes to be batched
47
54
  */
48
55
  declare const batchSignalWrites: (callback: () => void) => void;
49
56
  /**
50
- * Run a function with signal reads in a tracking context (or temporarily untrack)
57
+ * Run a function with signal reads in a tracking context (or temporarily untrack).
51
58
  *
52
59
  * @param {Watcher | false} watcher - Watcher to be called when the signal changes
53
60
  * or false for temporary untracking while inserting auto-hydrating DOM nodes
@@ -56,10 +63,19 @@ declare const batchSignalWrites: (callback: () => void) => void;
56
63
  */
57
64
  declare const trackSignalReads: (watcher: Watcher | false, run: () => void) => void;
58
65
  /**
59
- * Emit a notification to listeners
66
+ * Trigger a hook.
67
+ *
68
+ * @param {Set<HookCallback> | undefined} callbacks - Callbacks to be called when the hook is triggered
69
+ * @param {readonly string[] | undefined} payload - Payload to be sent to listeners
70
+ * @return {Cleanup | undefined} Cleanup function to be called when the hook is unmounted
71
+ */
72
+ declare const triggerHook: (callbacks: Set<HookCallback> | undefined, payload?: readonly string[]) => Cleanup | undefined;
73
+ /**
74
+ * Check whether a hook type is handled in a signal.
60
75
  *
61
- * @param {Set<Listener>} listeners - Listeners to be notified
62
- * @param {Notifications[K]} payload - Payload to be sent to listeners
76
+ * @param {Hook} type - Type of hook to check
77
+ * @param {T} handled - List of handled hook types
78
+ * @returns {type is T[number]} - Whether the hook type is handled
63
79
  */
64
- declare const emitNotification: <T extends keyof Notifications>(listeners: Set<Listener<T>>, payload: Notifications[T]) => void;
65
- export { type Cleanup, type Watcher, type Notifications, type Listener, type Listeners, createWatcher, subscribeActiveWatcher, notifyWatchers, flushPendingReactions, batchSignalWrites, trackSignalReads, emitNotification, };
80
+ declare const isHandledHook: <T extends readonly Hook[]>(type: Hook, handled: T) => type is T[number];
81
+ export { type Cleanup, type MaybeCleanup, type Watcher, type Hook, type CleanupHook, type WatchHook, type HookCallback, type HookCallbacks, HOOK_ADD, HOOK_CHANGE, HOOK_CLEANUP, HOOK_REMOVE, HOOK_SORT, HOOK_WATCH, UNSET, createWatcher, subscribeActiveWatcher, notifyWatchers, flushPendingReactions, batchSignalWrites, trackSignalReads, triggerHook, isHandledHook, };
@@ -1,4 +1,3 @@
1
- declare const UNSET: any;
2
1
  declare const isString: (value: unknown) => value is string;
3
2
  declare const isNumber: (value: unknown) => value is number;
4
3
  declare const isSymbol: (value: unknown) => value is symbol;
@@ -14,6 +13,5 @@ declare const isRecordOrArray: <T extends Record<string | number, unknown> | Rea
14
13
  declare const isUniformArray: <T>(value: unknown, guard?: (item: T) => item is T & {}) => value is T[];
15
14
  declare const hasMethod: <T extends object & Record<string, (...args: unknown[]) => unknown>>(obj: T, methodName: string) => obj is T & Record<string, (...args: unknown[]) => unknown>;
16
15
  declare const isAbortError: (error: unknown) => boolean;
17
- declare const toError: (reason: unknown) => Error;
18
16
  declare const valueString: (value: unknown) => string;
19
- export { UNSET, isString, isNumber, isSymbol, isFunction, isAsyncFunction, isSyncFunction, isNonNullObject, isObjectOfType, isRecord, isRecordOrArray, isUniformArray, hasMethod, isAbortError, toError, valueString, };
17
+ export { isString, isNumber, isSymbol, isFunction, isAsyncFunction, isSyncFunction, isNonNullObject, isObjectOfType, isRecord, isRecordOrArray, isUniformArray, hasMethod, isAbortError, valueString, };