@zeix/cause-effect 0.18.0 → 0.18.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.
@@ -8,7 +8,7 @@ type DiffResult = {
8
8
  change: UnknownRecord;
9
9
  remove: UnknownRecord;
10
10
  };
11
- type KeyConfig<T> = string | ((item: T) => string);
11
+ type KeyConfig<T> = string | ((item: T) => string | undefined);
12
12
  type ListOptions<T extends {}> = {
13
13
  keyConfig?: KeyConfig<T>;
14
14
  watched?: () => Cleanup;
@@ -19,8 +19,8 @@ type List<T extends {}> = {
19
19
  [Symbol.iterator](): IterableIterator<State<T>>;
20
20
  readonly length: number;
21
21
  get(): T[];
22
- set(newValue: T[]): void;
23
- update(fn: (oldValue: T[]) => T[]): void;
22
+ set(next: T[]): void;
23
+ update(fn: (prev: T[]) => T[]): void;
24
24
  at(index: number): State<T> | undefined;
25
25
  keys(): IterableIterator<string>;
26
26
  byKey(key: string): State<T> | undefined;
@@ -37,23 +37,24 @@ type List<T extends {}> = {
37
37
  * Checks if two values are equal with cycle detection
38
38
  *
39
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
40
+ * @param a - First value to compare
41
+ * @param b - Second value to compare
42
+ * @param visited - Set to track visited objects for cycle detection
43
+ * @returns Whether the two values are equal
44
44
  */
45
+ declare function isEqual<T>(a: T, b: T, visited?: WeakSet<object>): boolean;
45
46
  /** Shallow equality check for string arrays */
46
47
  declare function keysEqual(a: string[], b: string[]): boolean;
47
- declare function isEqual<T>(a: T, b: T, visited?: WeakSet<object>): boolean;
48
+ declare function getKeyGenerator<T extends {}>(keyConfig?: KeyConfig<T>): [(item: T) => string, boolean];
48
49
  /**
49
50
  * Creates a reactive list with stable keys and per-item reactivity.
50
51
  *
51
52
  * @since 0.18.0
52
- * @param initialValue - Initial array of items
53
+ * @param value - Initial array of items
53
54
  * @param options - Optional configuration for key generation and watch lifecycle
54
55
  * @returns A List signal
55
56
  */
56
- declare function createList<T extends {}>(initialValue: T[], options?: ListOptions<T>): List<T>;
57
+ declare function createList<T extends {}>(value: T[], options?: ListOptions<T>): List<T>;
57
58
  /**
58
59
  * Checks if a value is a List signal.
59
60
  *
@@ -62,4 +63,4 @@ declare function createList<T extends {}>(initialValue: T[], options?: ListOptio
62
63
  * @returns True if the value is a List
63
64
  */
64
65
  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, };
66
+ export { type DiffResult, type KeyConfig, type List, type ListOptions, type UnknownRecord, createList, isEqual, isList, getKeyGenerator, keysEqual, TYPE_LIST, };
@@ -25,6 +25,12 @@ type Memo<T extends {}> = {
25
25
  * @template T - The type of value computed by the memo
26
26
  * @param fn - The computation function that receives the previous value
27
27
  * @param options - Optional configuration for the memo
28
+ * @param options.value - Optional initial value for reducer patterns
29
+ * @param options.equals - Optional equality function. Defaults to strict equality (`===`)
30
+ * @param options.guard - Optional type guard to validate values
31
+ * @param options.watched - Optional callback invoked when the memo is first watched by an effect.
32
+ * Receives an `invalidate` function to mark the memo dirty and trigger recomputation.
33
+ * Must return a cleanup function called when no effects are watching.
28
34
  * @returns A Memo object with a get() method
29
35
  *
30
36
  * @example
@@ -1,4 +1,4 @@
1
- import { type Cleanup, type ComputedOptions } from '../graph';
1
+ import { type Cleanup, type SignalOptions } from '../graph';
2
2
  /**
3
3
  * A read-only signal that tracks external input and updates a state value as long as it is active.
4
4
  *
@@ -8,7 +8,6 @@ type Sensor<T extends {}> = {
8
8
  readonly [Symbol.toStringTag]: 'Sensor';
9
9
  /**
10
10
  * Gets the current value of the sensor.
11
- * Updates its state value if the sensor is active.
12
11
  * When called inside another reactive context, creates a dependency.
13
12
  * @returns The sensor value
14
13
  * @throws UnsetSignalValueError If the sensor value is still unset when read.
@@ -22,6 +21,13 @@ type Sensor<T extends {}> = {
22
21
  * @param set - A function to set the observed value
23
22
  * @returns A cleanup function when the sensor stops being watched
24
23
  */
24
+ type SensorOptions<T extends {}> = SignalOptions<T> & {
25
+ /**
26
+ * Optional initial value. Avoids `UnsetSignalValueError` on first read
27
+ * before the watched callback fires.
28
+ */
29
+ value?: T;
30
+ };
25
31
  type SensorCallback<T extends {}> = (set: (next: T) => void) => Cleanup;
26
32
  /**
27
33
  * Creates a sensor that tracks external input and updates a state value as long as it is active.
@@ -29,12 +35,12 @@ type SensorCallback<T extends {}> = (set: (next: T) => void) => Cleanup;
29
35
  * no longer watched. This lazy activation pattern ensures resources are only consumed when needed.
30
36
  *
31
37
  * @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.
38
+ * @template T - The type of value produced by the sensor
39
+ * @param watched - The callback invoked when the sensor starts being watched, receives a `set` function and returns a cleanup function.
40
+ * @param options - Optional configuration for the sensor.
35
41
  * @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`
42
+ * before the watched callback fires. Essential for the mutable-object observation pattern.
43
+ * @param options.equals - Optional equality function. Defaults to strict equality (`===`). Use `SKIP_EQUALITY`
38
44
  * for mutable objects where the reference stays the same but internal state changes.
39
45
  * @param options.guard - Optional type guard to validate values.
40
46
  * @returns A read-only sensor signal.
@@ -63,7 +69,7 @@ type SensorCallback<T extends {}> = (set: (next: T) => void) => Cleanup;
63
69
  * }, { value: node, equals: SKIP_EQUALITY });
64
70
  * ```
65
71
  */
66
- declare function createSensor<T extends {}>(start: SensorCallback<T>, options?: ComputedOptions<T>): Sensor<T>;
72
+ declare function createSensor<T extends {}>(watched: SensorCallback<T>, options?: SensorOptions<T>): Sensor<T>;
67
73
  /**
68
74
  * Checks if a value is a Sensor signal.
69
75
  *
@@ -72,4 +78,4 @@ declare function createSensor<T extends {}>(start: SensorCallback<T>, options?:
72
78
  * @returns True if the value is a Sensor
73
79
  */
74
80
  declare function isSensor<T extends {} = unknown & {}>(value: unknown): value is Sensor<T>;
75
- export { createSensor, isSensor, type Sensor, type SensorCallback };
81
+ export { createSensor, isSensor, type Sensor, type SensorCallback, type SensorOptions, };
@@ -14,8 +14,8 @@ type BaseStore<T extends UnknownRecord> = {
14
14
  keys(): IterableIterator<string>;
15
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
16
  get(): T;
17
- set(newValue: T): void;
18
- update(fn: (oldValue: T) => T): void;
17
+ set(next: T): void;
18
+ update(fn: (prev: T) => T): void;
19
19
  add<K extends keyof T & string>(key: K, value: T[K]): K;
20
20
  remove(key: string): void;
21
21
  };
@@ -28,7 +28,7 @@ type Store<T extends UnknownRecord> = BaseStore<T> & {
28
28
  * Properties are accessible directly via proxy.
29
29
  *
30
30
  * @since 0.15.0
31
- * @param initialValue - Initial object value of the store
31
+ * @param value - Initial object value of the store
32
32
  * @param options - Optional configuration for watch lifecycle
33
33
  * @returns A Store with reactive properties
34
34
  *
@@ -39,7 +39,7 @@ type Store<T extends UnknownRecord> = BaseStore<T> & {
39
39
  * console.log(user.get()); // { name: 'Bob', age: 30 }
40
40
  * ```
41
41
  */
42
- declare function createStore<T extends UnknownRecord>(initialValue: T, options?: StoreOptions): Store<T>;
42
+ declare function createStore<T extends UnknownRecord>(value: T, options?: StoreOptions): Store<T>;
43
43
  /**
44
44
  * Checks if a value is a Store signal.
45
45
  *
@@ -36,6 +36,12 @@ type Task<T extends {}> = {
36
36
  * @template T - The type of value resolved by the task
37
37
  * @param fn - The async computation function that receives the previous value and an AbortSignal
38
38
  * @param options - Optional configuration for the task
39
+ * @param options.value - Optional initial value for reducer patterns
40
+ * @param options.equals - Optional equality function. Defaults to strict equality (`===`)
41
+ * @param options.guard - Optional type guard to validate values
42
+ * @param options.watched - Optional callback invoked when the task is first watched by an effect.
43
+ * Receives an `invalidate` function to mark the task dirty and trigger re-execution.
44
+ * Must return a cleanup function called when no effects are watching.
39
45
  * @returns A Task object with get(), isPending(), and abort() methods
40
46
  *
41
47
  * @example
@@ -1,161 +0,0 @@
1
- # Collection Refactoring Plan
2
-
3
- ## Goal
4
-
5
- Unify `createCollection()` and `createSourceCollection()` into a single `createCollection()` primitive whose primary form mirrors `createSensor()`: an externally-driven signal with a watched lifecycle. The derived-from-List/Collection form becomes an internal helper used by `.deriveCollection()`.
6
-
7
- ## Motivation
8
-
9
- - **Sensor ↔ Collection parallel**: Both are externally-driven, lazily activated, and auto-cleaned. Making their signatures parallel sharpens this mental model.
10
- - **One primitive, one name**: Users learn `createCollection(start, options)` the same way they learn `createSensor(start, options)`.
11
- - **Derived collections are a method, not a standalone call**: `list.deriveCollection(fn)` and `collection.deriveCollection(fn)` already exist and are the natural way to create derived collections.
12
-
13
- ## New API Surface
14
-
15
- ```typescript
16
- // Primary form — externally driven (replaces createSourceCollection)
17
- function createCollection<T extends {}>(
18
- start: CollectionCallback<T>,
19
- options?: CollectionOptions<T>,
20
- ): Collection<T>
21
-
22
- // CollectionCallback mirrors SensorCallback but receives applyChanges
23
- type CollectionCallback<T extends {}> = (
24
- applyChanges: (changes: DiffResult) => void,
25
- ) => Cleanup
26
-
27
- // CollectionOptions — initial value hidden in options (like Memo, Task, Sensor)
28
- type CollectionOptions<T extends {}> = {
29
- value?: T[] // initial items (default: [])
30
- keyConfig?: KeyConfig<T> // key generation strategy
31
- createItem?: (key: string, value: T) => Signal<T> // custom item factory
32
- }
33
-
34
- // Derive method — unchanged on List and Collection
35
- collection.deriveCollection(callback)
36
- list.deriveCollection(callback)
37
- ```
38
-
39
- ## Refactoring Steps
40
-
41
- Order matters: the existing `createCollection` and `CollectionCallback` names must be freed up before they can be reused for the new concept. The refactoring proceeds in two phases.
42
-
43
- ### Phase 1 — Rename existing symbols (free the names)
44
-
45
- #### 1.1. Rename `createCollection` → `deriveCollection`
46
-
47
- In `src/nodes/collection.ts`:
48
-
49
- - Rename the function `createCollection(source, callback)` → `deriveCollection(source, callback)`.
50
- - Update both overload signatures and the implementation signature.
51
- - Update the internal `deriveCollection()` call inside the `Collection.deriveCollection` method body (both in the derived-collection object and the source-collection object).
52
-
53
- #### 1.2. Rename `CollectionCallback<T, U>` → `DeriveCollectionCallback<T, U>`
54
-
55
- - Rename the type alias in `src/nodes/collection.ts`.
56
- - Update all references: the `deriveCollection` parameter types, and the `Collection.deriveCollection` method parameter type annotations.
57
-
58
- #### 1.3. Update `list.ts`
59
-
60
- - Change the import from `createCollection` to `deriveCollection`.
61
- - Update `List.deriveCollection()` body to call `deriveCollection(list, cb)`.
62
-
63
- #### 1.4. Update exports in `index.ts`
64
-
65
- - Replace `createCollection` → `deriveCollection` in the export list.
66
- - Replace `CollectionCallback` → `DeriveCollectionCallback`.
67
- - Keep or drop `CollectionSource` from public exports (internal detail of `deriveCollection`).
68
-
69
- #### 1.5. Update tests
70
-
71
- - In `test/collection.test.ts` (or `test/collection.next.test.ts`): replace all direct `createCollection(source, cb)` calls with either `deriveCollection(source, cb)` or the equivalent `.deriveCollection(cb)` method.
72
- - Update imports accordingly.
73
-
74
- #### 1.6. Verify
75
-
76
- - `bun run check` and `bun test` pass.
77
- - Commit: "Rename createCollection → deriveCollection, CollectionCallback → DeriveCollectionCallback"
78
-
79
- ### Phase 2 — Reshape `createSourceCollection` → `createCollection`
80
-
81
- #### 2.1. Rename and reshape function
82
-
83
- In `src/nodes/collection.ts`:
84
-
85
- - Rename `createSourceCollection` → `createCollection`.
86
- - Move `initialValue` from first positional arg into `options.value` (default `[]`).
87
- - New signature: `createCollection<T>(start: CollectionCallback<T>, options?: CollectionOptions<T>)`.
88
-
89
- #### 2.2. Rename types
90
-
91
- - `SourceCollectionCallback` → `CollectionCallback<T>` (generic over `T` for type coherence with `CollectionOptions<T>`, even though the callback itself doesn't use `T` directly).
92
- - `SourceCollectionOptions<T>` → `CollectionOptions<T>`, adding the `value?: T[]` field.
93
-
94
- #### 2.3. Update exports in `index.ts`
95
-
96
- ```typescript
97
- // Remove
98
- export { createSourceCollection, SourceCollectionCallback, SourceCollectionOptions, CollectionSource }
99
-
100
- // Add / rename
101
- export { createCollection, CollectionCallback, CollectionOptions }
102
-
103
- // Keep
104
- export { Collection, DiffResult, isCollection }
105
-
106
- // Optional (if deriveCollection is exported)
107
- export { deriveCollection, DeriveCollectionCallback }
108
- ```
109
-
110
- #### 2.4. Update tests
111
-
112
- - `test/source-collection.test.ts` → rename to `test/collection.next.test.ts` (or merge into existing collection test file).
113
- - Update all `createSourceCollection(initialValue, start, options)` calls to `createCollection(start, { value: initialValue, ...options })`.
114
- - Update type imports: `CollectionCallback` instead of `SourceCollectionCallback`, etc.
115
-
116
- #### 2.5. Update CLAUDE.md and docs
117
-
118
- - Update the Collection section to present `createCollection(start, options)` as the primary form.
119
- - Show `.deriveCollection()` as the way to transform Lists/Collections.
120
- - Emphasize Sensor ↔ Collection parallel in the mental model section.
121
-
122
- #### 2.6. Verify
123
-
124
- - `bun run check` and `bun test` pass.
125
- - Commit: "Reshape createSourceCollection → createCollection(start, options)"
126
-
127
- ## Type Summary
128
-
129
- ```
130
- Before After
131
- ───────────────────────────────── ─────────────────────────────────
132
- createSourceCollection(init, start, opts) → createCollection(start, opts)
133
- SourceCollectionCallback → CollectionCallback<T>
134
- SourceCollectionOptions<T> → CollectionOptions<T>
135
-
136
- createCollection(source, callback) → deriveCollection(source, callback) [internal]
137
- CollectionCallback<T, U> → DeriveCollectionCallback<T, U> [internal or optional export]
138
- CollectionSource<T> → CollectionSource<T> [internal]
139
- ```
140
-
141
- ## Migration Checklist
142
-
143
- ### Phase 1 — Free the names
144
- - [ ] Rename `createCollection(source, cb)` → `deriveCollection(source, cb)`
145
- - [ ] Rename type `CollectionCallback<T, U>` → `DeriveCollectionCallback<T, U>`
146
- - [ ] Update `List.deriveCollection()` and `Collection.deriveCollection()` to call `deriveCollection()`
147
- - [ ] Update `index.ts` exports (phase 1)
148
- - [ ] Update tests (phase 1)
149
- - [ ] Verify: `bun run check` and `bun test` pass
150
- - [ ] Commit phase 1
151
-
152
- ### Phase 2 — Reclaim the names
153
- - [ ] Rename `createSourceCollection` → `createCollection(start, options)` with `options.value`
154
- - [ ] Rename type `SourceCollectionCallback` → `CollectionCallback<T>`
155
- - [ ] Rename type `SourceCollectionOptions` → `CollectionOptions` (add `value?: T[]`)
156
- - [ ] Drop `CollectionSource` from public exports
157
- - [ ] Update `index.ts` exports (phase 2)
158
- - [ ] Update tests (phase 2)
159
- - [ ] Update CLAUDE.md Collection sections
160
- - [ ] Verify: `bun run check` and `bun test` pass
161
- - [ ] Commit phase 2