Objects & random
import { deepmerge, random, randomInt, randomItem } from '@studiometa/js-toolkit/utils';Merging
deepmerge
deepmerge(...layers: Record<string, unknown>[]): Record<string, unknown>import { deepmerge } from '@studiometa/js-toolkit/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
import { random, randomInt, randomItem } from '@studiometa/js-toolkit/utils';
random(10); // 0 → 10
random(5, 10); // 5 → 10
randomInt(10); // an integer, 0 → 10
randomItem(['a', 'b', 'c']);One argument is a maximum; two are a range.
random
random(a: number, b?: number): numberA float in the range.
randomInt
randomInt(a: number, b?: number): numberAn integer in the range, bounds inclusive.
randomItem
randomItem<T>(items: readonly T[]): T | undefined
randomItem(items: string): string | undefinedOne item from an array, or one character from a string.
The 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.