@mmstack/primitives 20.0.0 → 20.0.1

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/index.d.ts CHANGED
@@ -98,6 +98,101 @@ declare function debounced<T>(initial: T, opt?: CreateDebouncedOptions<T>): Debo
98
98
  */
99
99
  declare function debounce<T>(source: WritableSignal<T>, opt?: CreateDebouncedOptions<T>): DebouncedSignal<T>;
100
100
 
101
+ /**
102
+ * A `MutableSignal` is a special type of `WritableSignal` that allows for in-place mutation of its value.
103
+ * In addition to the standard `set` and `update` methods, it provides a `mutate` method. This is useful
104
+ * for performance optimization when dealing with complex objects or arrays, as it avoids unnecessary
105
+ * object copying.
106
+ *
107
+ * @typeParam T - The type of value held by the signal.
108
+ */
109
+ type MutableSignal<T> = WritableSignal<T> & {
110
+ /**
111
+ * Mutates the signal's value in-place. This is similar to `update`, but it's optimized for
112
+ * scenarios where you want to modify the existing object directly rather than creating a new one.
113
+ *
114
+ * @param updater - A function that takes the current value as input and modifies it directly.
115
+ *
116
+ * @example
117
+ * const myArray = mutable([1, 2, 3]);
118
+ * myArray.mutate((arr) => {
119
+ * arr.push(4);
120
+ * return arr;
121
+ * }); // myArray() now returns [1, 2, 3, 4]
122
+ */
123
+ mutate: WritableSignal<T>['update'];
124
+ /**
125
+ * Mutates the signal's value in-place, similar to `mutate`, but with a void-returning value in updater
126
+ * function. This further emphasizes that the mutation is happening inline, improving readability
127
+ * in some cases.
128
+ * @param updater - Function to change to the current value
129
+ * @example
130
+ * const myObject = mutable({ a: 1, b: 2 });
131
+ * myObject.inline((obj) => (obj.a = 3)); // myObject() now returns { a: 3, b: 2 }
132
+ */
133
+ inline: (updater: (value: T) => void) => void;
134
+ };
135
+ /**
136
+ * Creates a `MutableSignal`. This function overloads the standard `signal` function to provide
137
+ * the additional `mutate` and `inline` methods.
138
+ *
139
+ * @typeParam T The type of value held by the signal.
140
+ * @param initial The initial value of the signal.
141
+ * @param options Optional signal options, including a custom `equal` function.
142
+ * @returns A `MutableSignal` instance.
143
+ *
144
+ * ### Important Note on `computed` Signals
145
+ *
146
+ * When creating a `computed` signal that derives a non-primitive value (e.g., an object or array)
147
+ * from a `mutable` signal, you **must** provide the `{ equal: false }` option to the `computed`
148
+ * function.
149
+ *
150
+ * This is because a `.mutate()` call notifies its dependents that it has changed, but if the
151
+ * reference to a derived object hasn't changed, the `computed` signal will not trigger its
152
+ * own dependents by default.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * const state = mutable({ user: { name: 'John' }, lastUpdated: new Date() });
157
+ *
158
+ * // ✅ CORRECT: Deriving a primitive value works as expected.
159
+ * const name = computed(() => state().user.name);
160
+ *
161
+ * // ❌ INCORRECT: This will not update reliably after the first change.
162
+ * const userObject = computed(() => state().user);
163
+ *
164
+ * // ✅ CORRECT: For object derivations, `equal: false` is required.
165
+ * const userObjectFixed = computed(() => state().user, { equal: false });
166
+ *
167
+ * // This mutation will now correctly trigger effects depending on `userObjectFixed`.
168
+ * state.mutate(s => s.lastUpdated = new Date());
169
+ * ```
170
+ */
171
+ declare function mutable<T>(): MutableSignal<T | undefined>;
172
+ declare function mutable<T>(initial: T): MutableSignal<T>;
173
+ declare function mutable<T>(initial: T, opt?: CreateSignalOptions<T>): MutableSignal<T>;
174
+ /**
175
+ * Type guard function to check if a given `WritableSignal` is a `MutableSignal`. This is useful
176
+ * for situations where you need to conditionally use the `mutate` or `inline` methods.
177
+ *
178
+ * @typeParam T - The type of the signal's value (optional, defaults to `any`).
179
+ * @param value - The `WritableSignal` to check.
180
+ * @returns `true` if the signal is a `MutableSignal`, `false` otherwise.
181
+ *
182
+ * @example
183
+ * const mySignal = signal(0);
184
+ * const myMutableSignal = mutable(0);
185
+ *
186
+ * if (isMutable(mySignal)) {
187
+ * mySignal.mutate(x => x + 1); // This would cause a type error, as mySignal is not a MutableSignal.
188
+ * }
189
+ *
190
+ * if (isMutable(myMutableSignal)) {
191
+ * myMutableSignal.mutate(x => x + 1); // This is safe.
192
+ * }
193
+ */
194
+ declare function isMutable<T = any>(value: WritableSignal<T>): value is MutableSignal<T>;
195
+
101
196
  /**
102
197
  * Options for creating a derived signal using the full `derived` function signature.
103
198
  * @typeParam T - The type of the source signal's value (parent).
@@ -201,6 +296,29 @@ declare function derived<T extends UnknownObject, TKey extends keyof T>(source:
201
296
  * ```
202
297
  */
203
298
  declare function derived<T extends any[]>(source: WritableSignal<T>, index: number, opt?: CreateSignalOptions<T[number]>): DerivedSignal<T, T[number]>;
299
+ /**
300
+ * Creates a `DerivedSignal` that derives its value from another `MutableSignal`.
301
+ * Use mutuable signals with caution, but very useful for deeply nested structures.
302
+ *
303
+ * @typeParam T The type of the source signal's value.
304
+ * @typeParam U The type of the derived signal's value.
305
+ * @param source The source `WritableSignal`.
306
+ * @param options An object containing the `from` and `onChange` functions, and optional signal options.
307
+ * @returns A `DerivedSignal & MutableSignal` instance.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * const user = signal({ name: 'John', age: 30 });
312
+ * const name = derived(user, {
313
+ * from: (u) => u.name,
314
+ * onChange: (newName) => user.update((u) => ({ ...u, name: newName })),
315
+ * });
316
+ *
317
+ * name.set('Jane'); // Updates the original signal
318
+ * console.log(user().name); // Outputs: Jane
319
+ * ```
320
+ */
321
+ declare function derived<T, U>(source: MutableSignal<T>, optOrKey: CreateDerivedOptions<T, U> | keyof T, opt?: CreateSignalOptions<U>): DerivedSignal<T, U> & MutableSignal<U>;
204
322
  /**
205
323
  * Creates a "fake" `DerivedSignal` from a simple value. This is useful for creating
206
324
  * `FormControlSignal` instances that are not directly derived from another signal.
@@ -362,101 +480,6 @@ declare function elementVisibility(target?: ElementRef<Element> | Element | Sign
362
480
  */
363
481
  declare function mapArray<T, U>(source: () => T[], map: (value: Signal<T>, index: number) => U, opt?: CreateSignalOptions<T>): Signal<U[]>;
364
482
 
365
- /**
366
- * A `MutableSignal` is a special type of `WritableSignal` that allows for in-place mutation of its value.
367
- * In addition to the standard `set` and `update` methods, it provides a `mutate` method. This is useful
368
- * for performance optimization when dealing with complex objects or arrays, as it avoids unnecessary
369
- * object copying.
370
- *
371
- * @typeParam T - The type of value held by the signal.
372
- */
373
- type MutableSignal<T> = WritableSignal<T> & {
374
- /**
375
- * Mutates the signal's value in-place. This is similar to `update`, but it's optimized for
376
- * scenarios where you want to modify the existing object directly rather than creating a new one.
377
- *
378
- * @param updater - A function that takes the current value as input and modifies it directly.
379
- *
380
- * @example
381
- * const myArray = mutable([1, 2, 3]);
382
- * myArray.mutate((arr) => {
383
- * arr.push(4);
384
- * return arr;
385
- * }); // myArray() now returns [1, 2, 3, 4]
386
- */
387
- mutate: WritableSignal<T>['update'];
388
- /**
389
- * Mutates the signal's value in-place, similar to `mutate`, but with a void-returning value in updater
390
- * function. This further emphasizes that the mutation is happening inline, improving readability
391
- * in some cases.
392
- * @param updater - Function to change to the current value
393
- * @example
394
- * const myObject = mutable({ a: 1, b: 2 });
395
- * myObject.inline((obj) => (obj.a = 3)); // myObject() now returns { a: 3, b: 2 }
396
- */
397
- inline: (updater: (value: T) => void) => void;
398
- };
399
- /**
400
- * Creates a `MutableSignal`. This function overloads the standard `signal` function to provide
401
- * the additional `mutate` and `inline` methods.
402
- *
403
- * @typeParam T The type of value held by the signal.
404
- * @param initial The initial value of the signal.
405
- * @param options Optional signal options, including a custom `equal` function.
406
- * @returns A `MutableSignal` instance.
407
- *
408
- * ### Important Note on `computed` Signals
409
- *
410
- * When creating a `computed` signal that derives a non-primitive value (e.g., an object or array)
411
- * from a `mutable` signal, you **must** provide the `{ equal: false }` option to the `computed`
412
- * function.
413
- *
414
- * This is because a `.mutate()` call notifies its dependents that it has changed, but if the
415
- * reference to a derived object hasn't changed, the `computed` signal will not trigger its
416
- * own dependents by default.
417
- *
418
- * @example
419
- * ```ts
420
- * const state = mutable({ user: { name: 'John' }, lastUpdated: new Date() });
421
- *
422
- * // ✅ CORRECT: Deriving a primitive value works as expected.
423
- * const name = computed(() => state().user.name);
424
- *
425
- * // ❌ INCORRECT: This will not update reliably after the first change.
426
- * const userObject = computed(() => state().user);
427
- *
428
- * // ✅ CORRECT: For object derivations, `equal: false` is required.
429
- * const userObjectFixed = computed(() => state().user, { equal: false });
430
- *
431
- * // This mutation will now correctly trigger effects depending on `userObjectFixed`.
432
- * state.mutate(s => s.lastUpdated = new Date());
433
- * ```
434
- */
435
- declare function mutable<T>(): MutableSignal<T | undefined>;
436
- declare function mutable<T>(initial: T): MutableSignal<T>;
437
- declare function mutable<T>(initial: T, opt?: CreateSignalOptions<T>): MutableSignal<T>;
438
- /**
439
- * Type guard function to check if a given `WritableSignal` is a `MutableSignal`. This is useful
440
- * for situations where you need to conditionally use the `mutate` or `inline` methods.
441
- *
442
- * @typeParam T - The type of the signal's value (optional, defaults to `any`).
443
- * @param value - The `WritableSignal` to check.
444
- * @returns `true` if the signal is a `MutableSignal`, `false` otherwise.
445
- *
446
- * @example
447
- * const mySignal = signal(0);
448
- * const myMutableSignal = mutable(0);
449
- *
450
- * if (isMutable(mySignal)) {
451
- * mySignal.mutate(x => x + 1); // This would cause a type error, as mySignal is not a MutableSignal.
452
- * }
453
- *
454
- * if (isMutable(myMutableSignal)) {
455
- * myMutableSignal.mutate(x => x + 1); // This is safe.
456
- * }
457
- */
458
- declare function isMutable<T = any>(value: WritableSignal<T>): value is MutableSignal<T>;
459
-
460
483
  /**
461
484
  * Creates a read-only signal that reactively tracks whether a CSS media query
462
485
  * string currently matches.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/primitives",
3
- "version": "20.0.0",
3
+ "version": "20.0.1",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",