Strict, dependency-free TypeScript utilities, copied into the simulation UIs (Angular) that refresh values up to ~1 000 times per second: numbers, SVG elements as seen on screen and gauges drawn around them, colors, enums, guards, strings and types.
- One function or class per file, with the types that belong to it, in a folder per theme with its
index.tsand its specs. Copy a folder into another project;docs/FUNCTIONS.mdlists the files each function needs. - Simple and fast: trusted inputs, no allocation in hot paths, caches only where they always pay, every optimisation measured in
benchmarks/. - Strict: TypeScript strict mode, an exhaustive ESLint configuration, 100 % test coverage (the SVG specs run in Chromium), every export documented with an example.
The requirements and design decisions are in docs/SPEC.md; the coding rules in AGENTS.md; the signatures of the functions removed until a project needs them in docs/REMOVED.md. The wiki (one searchable page per function, with its tests and source) is published on GitHub Pages at each push to main: https://alexandregallais.github.io/utils/.
- Angles are in degrees, 0° up and clockwise (SVG coordinates, compass headings), everywhere.
- Defaults in the signature for the settings (
formatNumber(value),svgRotateBy(15)); nonull: passvalue ?? undefinedto get a default. - Inputs are trusted: no argument validation; a
parse…function throws a singleTypeErrorwhen its text does not match the expected format. - Arguments are never mutated: functions return new arrays and objects.
import { formatNumber, remap, roundToStep } from 'utils';
label.textContent = formatNumber(speed, '1.1-1'); // '12.5'
const angle = remap(speed, 0, 40, -135, 135, true); // a needle angle, stopped at the ends
const setpoint = roundToStep(dragged, 0.5); // 12.5, without float noiseimport { applySvgTransforms, createSvgArcPath, createSvgArcTicksPath, svgFlipTo, svgPlaceOn, svgRotateBy, svgRotateTo } from 'utils';
// a round gauge drawn around its hub, whatever the groups of each element
const arc = { center: hub, radius: 40, startAngle: -135, sweepAngle: 270 };
track.setAttribute('d', createSvgArcPath(track, arc));
majorTicks.setAttribute('d', createSvgArcTicksPath(majorTicks, arc, 4, 8));
// the needle: unmirrored, upright, its foot on the hub, then turned at each frame
const rotation = svgRotateBy(0, 'center', hub);
applySvgTransforms(needle, [svgFlipTo(false), svgRotateTo(0), svgPlaceOn(hub, 'center', 'bottom'), rotation]);
rotation.set(angle, 'center', hub);Generated from the JSDoc of the sources (pnpm docs:catalog); each name links to its file, where the full documentation and examples are.
Async: waiting, cancellable with an abort signal.
| Export | What it does |
|---|---|
sleep |
Waits for a delay, cancellable with an AbortSignal that clears the timer. |
Colors: the fill of an SVG element, and the black or white text that reads best on it.
| Export | What it does |
|---|---|
getContrastingTextColor |
Picks the text color, pure black or pure white, that reads best on a background. |
getSvgFillColor |
Reads the fill color of an SVG element as rendered, whatever sets it: attribute, class, inherited style. |
Rgb (type) |
An sRGB color, each channel from 0 to 255. |
Durations: parsing a .NET TimeSpan into milliseconds.
| Export | What it does |
|---|---|
parseTimeSpan |
Parses a .NET TimeSpan into milliseconds: the constant format c (1.02:03:04.5670000, the JSON one) or the general formats g / G (1:02:03:04.567). |
Enums: listing members, checking and reading untyped values, literal types.
| Export | What it does |
|---|---|
EnumLiteral (type) |
Turns an enum type into the union of its literal values, so plain literals are accepted where the enum is expected: 'idle' | 'running' for a string enum, 0 | 1 for a numeric one. |
EnumObject (type) |
Any TypeScript enum, or a const object used as one. |
getEnumEntries |
Lists the members of an enum as [name, value] pairs, without the reverse mapping of numeric enums. |
getEnumKey |
Finds the member name of an enum value, string enums included: a readable name for a log or a translation key. |
getEnumKeys |
Lists the member names of an enum, without the reverse mapping of numeric enums. |
getEnumValues |
Lists the values of an enum, without the names of the reverse mapping of numeric enums. |
isEnumValue |
Checks whether a value is one of the values of an enum, in O(1): the values of each enum are read once. |
parseEnumValue |
Reads an enum member from a text, such as a query parameter: '1' gives the member of value 1. |
toEnumValue |
Returns a value typed as an enum member: the value itself when it belongs to the enum, the fallback otherwise. |
Formatting: numbers as display text.
| Export | What it does |
|---|---|
formatDecimal |
Formats a number with . before the decimals, no trailing zeros, no scientific notation and no -0. |
formatNumber |
Formats a number like Angular's DecimalPipe: digits driven by digitsInfo, separators of a locale. |
Guards: type guards and assertions that narrow values (the enum guards are in enum).
| Export | What it does |
|---|---|
assert |
Throws when a condition is falsy, and narrows its type otherwise. |
assertNever |
Marks a code path as unreachable: in the default of a switch over a union, TypeScript fails when a member is not handled, and an unexpected value throws at run time. |
isArray |
Checks whether a value is an array, whatever its items. |
isArrayOf |
Checks whether a value is an array whose every item passes a type guard. |
isBoolean |
Checks whether a value is a boolean. |
isDefined |
Checks whether a value is neither null nor undefined: the opposite of isNullish. |
isFiniteNumber |
Checks whether a value is a finite number: neither NaN nor an infinity. |
isFunction |
|
isNonEmptyArray |
Checks whether an array has an item, and narrows it so that its first item is defined. |
isNotUndefined |
Checks whether a value is not undefined; null passes, unlike with isDefined. |
isNull |
Checks whether a value is null; undefined does not pass. |
isNullish |
Checks whether a value is null or undefined: the opposite of isDefined. |
isNumber |
Checks whether a value is a number other than NaN. |
isObject |
Checks whether a value is a non-null object: plain object, array, class instance, date… isRecord accepts plain objects only. |
isRecord |
Checks whether a value is a plain object: an object literal or Object.create(null). |
isString |
Checks whether a value is a string primitive. |
isUndefined |
Checks whether a value is undefined; null does not pass. |
Math: clamping, interpolation, wrapping, rounding without float noise, smoothing.
| Export | What it does |
|---|---|
applyHysteresis |
Updates an on/off state with hysteresis: on at or above highThreshold, off at or below lowThreshold, unchanged in between. |
ceilToStep |
Rounds a number up to a multiple of a step, without float noise. |
clamp |
Restricts a number to an interval. |
floorToStep |
Rounds a number down to a multiple of a step, without float noise. |
hasSignificantChange |
Checks whether a value moved enough since the displayed one to be worth a render (a deadband). |
interpolateTable |
Reads a lookup table with linear interpolation between its points, such as a calibration curve or a tank gauging table. |
inverseLerp |
Computes where a value sits between two numbers: the inverse of lerp. |
isBetween |
Checks whether a number lies between two bounds. |
isNearlyEqual |
Checks whether two numbers are equal within a tolerance, relative for large numbers and absolute near zero. |
lerp |
Interpolates linearly between two numbers, exact at both ends and extrapolated outside [0, 1]. |
moveTowards |
Moves a value towards a target at a limited rate (a slew-rate limiter), whatever the frame rate: a needle or a rudder that cannot jump. |
ratio |
Divides a value by a total, without NaN or Infinity. |
remap |
Maps a value from an input range to an output range, such as a sensor reading to a gauge angle. |
roundToFractionDigits |
Rounds a number to a number of decimals, half away from zero, with plain arithmetic: the same result as Number(formatDecimal(value, maxFractionDigits)), much faster. |
roundToSignificantDigits |
Rounds a number to a number of significant digits, whatever its magnitude. |
roundToStep |
Rounds a number to the nearest multiple of a step, without float noise: roundToStep(0.3, 0.1) is 0.3. |
smoothTowards |
Moves a value towards a target with exponential smoothing, whatever the frame rate: about 63 % of the distance is covered every timeConstantMs. |
wrap |
Wraps a number into [min, max[ with an always-positive modulo, like a heading wrapping at 360°. |
Objects: picking, omitting, mapping values, emptiness and equality.
| Export | What it does |
|---|---|
deepMerge |
Applies a partial patch to nested settings, such as saved preferences over the defaults: plain objects are merged at every depth, other values replace, undefined keeps. |
isDeepEqual |
Checks whether two values are equal at every depth: primitives (NaN equals NaN), arrays, objects with the same prototype, Date, RegExp, Map, Set and typed arrays. |
isEmpty |
Checks whether a container holds nothing: an empty string, array, Map, Set or object. |
isShallowEqual |
Checks whether two values are equal at the first level, with Object.is: skips an update when a new object carries the same values as the previous one. |
mapValues |
Transforms every value of an object, keeping its keys. |
omit |
Copies an object without some of its properties, without delete, which slows down V8. |
pick |
Copies some properties of an object into a new one. |
Paths: joining URL segments.
| Export | What it does |
|---|---|
joinPath |
Joins URL path segments with /, ignoring empty segments and collapsing repeated slashes, except the // of a protocol. |
Strings: case conversion, word splitting, truncation, escaping, templating, number extraction.
| Export | What it does |
|---|---|
camelCase |
Converts a string to camelCase; acronyms are words (XMLHttpRequest → xmlHttpRequest). |
capitalize |
Upper-cases the first character of a string, even outside the basic Unicode plane. |
constantCase |
Converts a string to CONSTANT_CASE: uppercase words joined with _, as in constants and environment variables. |
createIdGenerator |
Creates a generator of readable unique ids: gauge-1, gauge-2… for SVG gradients, clip paths or markers. |
dotCase |
Converts a string to dot.case: lower-case words joined with ., as in translation keys and property paths. |
escapeHtml |
Escapes the characters that have a meaning in HTML (& < > " '), to insert a text in markup. |
escapeRegExp |
Escapes a text so that it matches literally in a regular expression, v flag included. |
extractNumber |
Extracts the first number of a text, with . or , before the decimals: 1,234 is 1.234. |
extractNumbers |
Extracts every number of a text, with the rules of extractNumber. |
formatTemplate |
Replaces the {key} placeholders of a template with values. |
isBlank |
Checks whether a text is missing or holds only whitespace: a required field left empty. |
kebabCase |
Converts a string to kebab-case: lowercase words joined with -, as in file names, CSS classes and URLs. |
lowerCase |
Converts a string to lower-case words separated by spaces, splitting identifiers such as camelCase. |
pascalCase |
Converts a string to PascalCase: words joined, each capitalized, the rest in lower case. |
pluralize |
Picks the singular or plural form of a word for a count. |
removeDiacritics |
Removes the accents of a text (Été → Ete), to search or sort without them. |
sentenceCase |
Converts an identifier to a sentence, to turn a key into a label: the first word capitalized, the others in lower case, acronyms kept. |
slugify |
Turns a text into a URL- and id-friendly slug: accents removed, lower-case words joined with -. |
snakeCase |
Converts a string to snake_case: lowercase words joined with _, as in database columns and JSON keys. |
splitWords |
Splits a string into words, whatever its case style (camelCase, kebab-case, snake_case, spaces…), in one pass. |
squish |
Trims a text and collapses every run of whitespace, line breaks included, into a single space. |
titleCase |
Converts a string to Title Case: words separated by spaces, each capitalized, acronyms kept. |
toCsv |
Builds CSV text (RFC 4180): fields are quoted when needed, lines end with CRLF. |
trainCase |
Converts a string to Train-Case: capitalized words joined with -, as in HTTP header names. |
truncate |
Shortens a string to a maximum length, with an ellipsis when it is cut. |
uncapitalize |
Lower-cases the first character of a string and leaves the rest unchanged: the reverse of capitalize. |
upperCase |
Converts a string to upper-case words separated by spaces, splitting identifiers such as camelCase. |
SVG shapes: the d of arcs, ticks and pies around an element, and of ranges and ticks along a bar, in any group.
| Export | What it does |
|---|---|
createSvgArcPath |
Creates the d of an arc around the center of an element, in any group: the track of a gauge around its hub. |
createSvgArcTicksPath |
Creates the d of evenly spaced graduations along an arc, around the center of an element in any group: count intervals give count + 1 ticks. |
createSvgBarRangePath |
Creates the d of the part of a bar between two ratios, across its whole thickness, in any group: the fill level of a bar graph (0 to the value) or a threshold zone. |
createSvgBarTicksPath |
Creates the d of evenly spaced graduations along a bar, in any group: count intervals give count + 1 ticks, across the bar from one of its sides. |
createSvgPiePath |
Creates the d of a pie slice joined to the center of an element, in any group: a radar sector, a remaining-time disk, to fill. |
getSvgArcPoint |
Finds a point along an arc, in the coordinates of an element of any group: where to put the label of a graduation or a marker. |
SvgArc (type) |
An arc around the center of an element, for createSvgArcPath and its siblings (0° up, clockwise). |
SvgBar (type) |
A bar gauge laid on the box of an element, for createSvgBarRangePath and createSvgBarTicksPath. |
SVG transforms: orders applied one after the other and changed later, and anchors across groups.
| Export | What it does |
|---|---|
Anchor (type) |
One of the 9 points of a box: its corners, the middles of its sides, its center. |
applySvgTransforms |
Applies orders to an SVG element, in order, each on what the previous ones give: one transform per order is added at the end of its transform list. |
clearSvgTransforms |
Empties the transform list of an SVG element: it is drawn without any transform. |
getSvgAnchorPoint |
Finds an anchor of the box an SVG element takes on screen: a corner, the middle of a side or the center. |
getSvgAnchorPointIn |
Finds an anchor of an element as seen on screen, in the coordinates of another element, whatever groups lie between: the center of a hub for the setRotate of a needle drawn in another group. |
svgFlipTo |
An order of applySvgTransforms that makes the element mirrored or not, as seen on screen, around one of its anchors: svgFlipTo(false) makes a mirrored text readable again. |
svgPlaceOn |
An order of applySvgTransforms that moves the element so that one of its anchors lands on an anchor of another element, as seen on screen, whatever groups each one is in. |
svgRotateBy |
An order of applySvgTransforms that turns the element by an angle from its position before this order, clockwise on screen, around an anchor of itself or of another element: set(90) after set(3) gives 90° from the start, not 93°. |
svgRotateTo |
An order of applySvgTransforms that turns the element to an absolute angle on screen, clockwise from upright, around an anchor of itself or of another element: svgRotateTo(0) straightens it. |
svgScaleBy |
An order of applySvgTransforms that enlarges or shrinks the element in its own axes, around an anchor of its drawing, which gives the direction: with 'left', it grows to the right; with 'bottom', upwards. |
SvgTransformOrder (type) |
An order of applySvgTransforms, created by svgRotateBy, svgRotateTo, svgFlipTo, svgScaleBy, svgTranslateBy or svgPlaceOn: keep it to change its values later with set. |
svgTranslateBy |
An order of applySvgTransforms that moves the element along its own axes, in its own units: turned by 90°, "to the right" goes down on screen. |
Types: TypeScript utility types.
| Export | What it does |
|---|---|
AnyFunction (type) |
A type for any function, for generic constraints such as F extends AnyFunction (safer than Function, which also accepts classes and has an untyped call). |
Awaitable (type) |
A value that is returned directly or through a promise: the result of a callback that may be sync or async. |
Brand (type) |
A nominal type: a T that cannot be mixed up with another T of a different brand. |
Constructor (type) |
A type for any class producing a T: mixins, dependency injection tokens, instanceof helpers. |
DeepPartial (type) |
Makes every property optional, at every depth: a patch of a nested settings object, read-only since a patch is only read. |
DeepReadonly (type) |
Makes every property and array read-only, at every depth: a frozen configuration, a state snapshot that must not be changed in place. |
ElementOf (type) |
Reads the item type of an array or tuple, such as the type of a const list of options. |
Entries (type) |
The [key, value] pairs of an object type, each key with its own value type: the precise type of Object.entries, which widens keys to string. |
FirstParameter (type) |
Reads the type of the first parameter of a function, such as the event of a handler. |
KeysOfType (type) |
Lists the keys of the properties whose type matches: the numeric fields of a record to chart, the boolean flags of a settings object. |
LiteralUnion (type) |
A union of known literals that still accepts any value of the base type, without losing autocompletion of the literals (a plain 'a' | 'b' | string collapses to string). |
Merge (type) |
Combines two object types, the properties of the second replacing those of the first (like an object spread { ...a, ...b }), where an intersection would give never for conflicting properties. |
Mutable (type) |
Removes readonly from the properties of a type, one level deep: a builder filling an object before handing it out as read-only. |
NonEmptyArray (type) |
An array with at least one item, so that its first item is never undefined. |
Nullable (type) |
A T that may be null. |
Nullish (type) |
A T that may be null or undefined. |
Optional (type) |
A T that may be undefined. |
PartialKeys (type) |
Makes some properties optional: the input of a factory that fills them with defaults. |
PickByType (type) |
Keeps the properties whose type matches. |
RequireKeys (type) |
Makes some optional properties required: the result of filling in defaults. |
Simplify (type) |
Flattens intersections and mapped types into a plain object type, so that editor tooltips show the properties instead of A & Omit<B, 'c'>. |
UnionToIntersection (type) |
Turns a union into the intersection of its members: A | B gives A & B, to merge the types of a list of mixins or handlers. |
ValueOf (type) |
The union of the value types of an object type, such as a const object used as an enum. |
Measured with pnpm bench in Chromium (Playwright, cross-origin isolated for precise timers), Apple Silicon,
1 000 calls per sample unless stated:
| Operation | Library | Baseline | Gain |
|---|---|---|---|
roundToFractionDigits |
arithmetic | Number(formatDecimal()) |
~41× |
formatNumber |
cached Intl formatter |
new Intl.NumberFormat() per call |
~40× |
formatDecimal |
cached Intl formatter |
new Intl.NumberFormat() per call |
~38× |
roundToStep |
no float noise | Math.round(v / step) * step (noisy) |
0.5× |
roundToStep is slower on purpose: it pays ~7 ns per call for exact results.
pnpm only (corepack enable makes the pinned version available).
pnpm install
pnpm exec playwright install chromium # once, for the SVG specs and the benchmarks
pnpm test # unit tests (watch: pnpm test:watch)
pnpm coverage # tests + coverage report (coverage/), 100 % required
pnpm lint # ESLint (in parallel), info rules off: errors fail (lint:strict: warnings too)
pnpm lint:css # Stylelint on every .scss file (lint:css:strict: warnings fail too)
pnpm lint:css:fix # Stylelint autofix: property order and Prettier formatting
pnpm lint:fix # ESLint autofix, imports rewritten through the folders' index.ts included
pnpm lint:editor # info rules shown in blue in VS Code
pnpm lint:presets # the lint presets of lint/ on the example design system
pnpm typecheck # every tsconfig project
pnpm knip # unused files, exports and dependencies
pnpm bench # benchmarks in Chromium
pnpm transfer lint # one text file that recreates lint/ elsewhere (transfer/)
pnpm docs:catalog # regenerate the function lists of README.md and docs/FUNCTIONS.md
pnpm wiki:dev # wiki (VitePress) with live reload: one page per export, search with Ctrl K
pnpm wiki:build # wiki with test results and coverage, in docs/.vitepress/dist
pnpm check # catalog, wiki pages, typecheck, ESLint, Stylelint, presets, format, knip, tests with coverageCommit messages follow Conventional Commits. To add a function, follow AGENTS.md → "Writing a function".