Objects & random
import { deepmerge, random, randomInt, randomItem } from '@studiometa/js-toolkit-v4/utils';deepmerge
deepmerge(...layers: Record<string, unknown>[]): Record<string, unknown>import { deepmerge } from '@studiometa/js-toolkit-v4/utils';
deepmerge({ tween: { ease: 'linear', duration: 300 } }, { tween: { ease: 'ease-out' } });
// { tween: { ease: 'ease-out', duration: 300 } }Later layers win, at every depth. Plain objects merge; anything else — an array, a Date, an element, a class instance — replaces.
That last rule is the one worth knowing: an array is a value, not a structure to merge, because merging two arrays by index is almost never what a caller meant.
It ships for the consumer, not for core
A utility is judged by consumer need, not by whether core calls it. Layering a default config under an author's config is the case every component author meets, and getting the array rule wrong is exactly how a hand-rolled merge misbehaves.
random and randomInt
random(a: number, b?: number): number
randomInt(a: number, b?: number): numberimport { random, randomInt } from '@studiometa/js-toolkit-v4/utils';
random(10); // 0 → 10
random(5, 10); // 5 → 10
randomInt(10); // an integer, 0 → 10
randomInt(5, 10); // an integer, 5 → 10One argument is a maximum; two are a range. The bounds are inclusive for randomInt.
randomItem
randomItem<T>(items: readonly T[]): T | undefined
randomItem(items: string): string | undefinedimport { randomItem } from '@studiometa/js-toolkit-v4/utils';
randomItem(['a', 'b', 'c']); // 'a' | 'b' | 'c' | undefined
randomItem('abc'); // 'a' | 'b' | 'c' | undefinedThe return includes undefined because an empty input has no item to give — and a signature that pretended otherwise would put the bug three lines later.
The string overload picks a character, which is the same question asked of a different sequence.