Skip to content

@lakutata/utils ​

The utilities of the lakutata framework. Time is a date and time in an IANA time zone, with a moment-style API (formatting, calendar arithmetic across daylight saving time changes, comparisons). The helpers, shared by the framework and its applications, cover strings, objects, arrays and sets, hashes and encodings, UUIDs, IP addresses, globs and statistics. They are plain functions and classes that can be used anywhere, inside an application or not. The umbrella package lakutata re-exports them as lakutata/utils, and also exports Time from lakutata itself.

Installation ​

shell
npm install lakutata

It has no optional dependencies: everything is built on Node.js (^22.13.0 || >=24.11.0) and its Intl time zone data.

Usage ​

The helpers in an application ​

Import the helpers where you need them; they do not need to be registered.

typescript
import {Application, Component, Time} from 'lakutata'
import {objectHash, randomString, templating, UUID} from 'lakutata/utils'

class Invitations extends Component {
    public invite(email: string): {id: string, code: string, key: string, message: string, expires: string} {
        const expires: Time = new Time().timezone('Europe/Paris').add(7, 'days').endOf('day')
        return {
            id: UUID.v4(),
            code: randomString(6, 'numeric'),
            //The same invitation always gives the same key
            key: objectHash({email: email}),
            message: templating('Your invitation to {app} expires on {date}', {app: 'Lakutata', date: expires.format('LL')}),
            expires: expires.format()
        }
    }
}

Application
    .run(() => ({
        id: 'utils.app',
        name: 'Utils',
        components: {
            invitations: {class: Invitations}
        }
    }))
    .onLaunched(async (app: Application): Promise<void> => {
        const invitations: Invitations = await app.getObject('invitations')
        console.log(invitations.invite('ada@example.com'))
    })

Strings, objects and collections ​

typescript
import {camelCase, mergeMap, noCase, ObjectPath, sortArray, sortObject, templating, uniqueArray} from 'lakutata/utils'

console.log(camelCase('user_id'), '|', noCase('XMLHttpRequest'))
//userId | xml http request

const config: Record<string, any> = {db: {hosts: [{name: 'primary', port: 5432}]}}
console.log(ObjectPath.get<number>(config, 'db.hosts[0].port'))
//5432
//{{name}} escapes the HTML, {name} does not
console.log(templating('<p>Hello {{name}}</p>', {name: '<b>Ada</b>'}))
//<p>Hello &lt;b&gt;Ada&lt;/b&gt;</p>

type Person = {name: string, age: number}
const people: Person[] = [{name: 'Bob', age: 30}, {name: 'Ada', age: 30}, {name: 'Cy', age: 20}]
//In place: the oldest first, by name among the same age
sortArray(people, {by: ['age', 'name'], order: ['desc', 'asc']})
console.log(people.map((person: Person): string => person.name))
//['Ada', 'Bob', 'Cy']
console.log(uniqueArray([3, 1, 3, 2]), Object.keys(sortObject({b: 1, a: 2})))
//[3, 1, 2] ['a', 'b']
console.log([...mergeMap(new Map([['a', 1]]), new Map([['a', 2], ['b', 3]]))])
//[['a', 2], ['b', 3]]

Hashes, encodings and identifiers ​

typescript
import {md5, MessagePack, objectHash, sha256, signedToHex, hexToSigned, UUID} from 'lakutata/utils'

console.log(sha256('hello').toString('hex').slice(0, 16), md5('hello').length)
//2cf24dba5fb0a30e 16
//Deeply equal values give the same hash, whatever the order of their keys
console.log(objectHash({a: 1, b: 2}) === objectHash({b: 2, a: 1}))
//true
const packed: string = MessagePack.stringify({id: 1, at: new Date(0)})
console.log(MessagePack.parse(packed))
//{id: 1, at: 1970-01-01T00:00:00.000Z}
console.log(signedToHex(-2, 16), hexToSigned('FFFE'))
//FFFE -2
const DNS: string = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'
console.log(UUID.validate(UUID.v4()), UUID.v5('example.com', DNS))
//true cfbff0d1-9375-5685-968c-48ce8b15ae17

Files, async and statistics ​

typescript
import {delay, Glob, pathExists, Statistics} from 'lakutata/utils'

if (await pathExists('./src')) {
    const sources: string[] = await Glob('src/**/*.ts')
    console.log(`${sources.length} TypeScript files`)
}
await delay(100)
const latencies: number[] = [12, 15, 11, 90, 14, 13]
console.log(Statistics.median(latencies), Statistics.quantile(latencies, 0.9))
//13.5 90

Time ​

A Time is an instant in a time zone: new Time() is the current time in the default zone (the TZ environment variable, else the system's zone), and timezone(name) gives the same instant in another zone. The methods of Time never modify the instance: setters and arithmetic return a new Time.

Time zones ​

typescript
import {Time} from 'lakutata'

//An ISO 8601 string with an offset (or Z) is exact; without one, it is read in the default time zone
const meeting: Time = new Time('2024-03-09T15:00:00Z')
console.log(meeting.timezone('Asia/Shanghai').format(), meeting.timezone('America/New_York').format())
//2024-03-09T23:00:00+08:00 2024-03-09T10:00:00-05:00
const paris: Time = meeting.timezone('Europe/Paris')
console.log(paris.timezone(), paris.utcOffset(), paris.hours())
//Europe/Paris 60 16
//Set the wall clock fields in the zone
console.log(paris.hours(9).minutes(30).format())
//2024-03-09T09:30:00+01:00
console.log(Time.timezones().includes('Asia/Kolkata'))
//true

A string without an offset, and an array of wall clock fields, are read in the default zone by the constructor, and in the instance's zone by its comparisons and diff() (paris.isSame('2024-03-09', 'day') compares with that day in Paris). timezone() throws a RangeError for a zone that the runtime does not know, and timezone('') gives the default zone. The methods inherited from Date (getHours(), setHours()…) use the process's time zone, and their setters modify the instance: use the Time methods instead.

Formatting ​

format() takes moment's format tokens (in English); text in brackets is kept as is.

typescript
import {Time} from 'lakutata'

const time: Time = new Time('2024-03-09T15:04:05.123Z').timezone('America/New_York')
console.log(time.format())
//2024-03-09T10:04:05-05:00
console.log(time.format('YYYY-MM-DD HH:mm:ss.SSS Z'))
//2024-03-09 10:04:05.123 -05:00
console.log(time.format('dddd, MMMM Do YYYY [at] h:mm A z'))
//Saturday, March 9th 2024 at 10:04 AM EST
console.log(time.format('LLLL'), '|', time.format('X'))
//Saturday, March 9, 2024 10:04 AM | 1709996645
console.log(time.toObject())
//{years: 2024, months: 2, date: 9, hours: 10, minutes: 4, seconds: 5, milliseconds: 123}

Arithmetic and comparisons ​

typescript
import {Time} from 'lakutata'

const start: Time = new Time('2024-03-09T12:00:00Z').timezone('America/New_York')
//The days keep the wall clock across the daylight saving time change, the hours move the instant
console.log(start.add(1, 'day').format(), start.add(24, 'hours').format())
//2024-03-10T07:00:00-04:00 2024-03-10T08:00:00-04:00
//A month without the day takes its last day
console.log(new Time('2024-01-31T12:00:00Z').timezone('UTC').add(1, 'month').format('YYYY-MM-DD'))
//2024-02-29
console.log(start.startOf('week').format(), start.endOf('month').format('YYYY-MM-DD HH:mm:ss.SSS'))
//2024-03-03T00:00:00-05:00 2024-03-31 23:59:59.999
console.log(start.add(1, 'day').diff(start, 'days'), start.add(1, 'day').diff(start, 'hours'))
//1 23
console.log(start.isSame('2024-03-09T23:00:00-05:00', 'day'), start.isBefore('2024-03-10T00:00:00-05:00'))
//true true
console.log(start.isBetween('2024-03-01', '2024-03-31', null, '[]'), Time.max(start, start.add(1, 'h')).format())
//true 2024-03-09T08:00:00-05:00
  • add() and subtract() move the wall clock for years, quarters, months, weeks and days (fractions are rounded: 1.5 days is 2 days), and the instant for hours, minutes, seconds and milliseconds.
  • A wall clock time skipped by a daylight saving time change (02:30 when the clocks jump from 02:00 to 03:00) moves forward by the length of the gap (to 03:30), whatever time it was computed from. A time that occurs twice (when the clocks are turned back) keeps the offset of the time it was computed from when possible, and is otherwise the earlier one.
  • diff() is this - other, truncated toward zero unless its third argument is true; months are counted as calendar months, and days as wall clock days.
  • An invalid time (an array field out of range) does not throw: its format() returns 'Invalid date', and Time.max() and Time.min() ignore it.

API ​

All of them are exported by lakutata/utils and @lakutata/utils, and Time also by lakutata. The typings document every declaration, with examples.

Time ​

ExportWhat it is
TimeA date and time in a time zone: timezone, the field getters and setters (year, month, date, hours, weekday, isoWeeks…), get/set, add/subtract, startOf/endOf, format, diff, isBefore/isSame/isAfter/isBetween, toObject, clone; the static timezones, max, min
TimeInputWhat new Time() and the comparisons accept: a Time, a Date, milliseconds, a date string, the wall clock fields
TimeObjectThe wall clock fields returned by toObject()
UnitOfTimeThe units of the Time methods ('day', 'days', 'd'…)

Strings ​

ExportWhat it is
camelCaseConvert a string of any case into camelCase
noCase, NoCaseOptions, SplitOptionsConvert a string of any case into lower case words separated by spaces
templating, TemplatingOptions, MissingValueErrorFill the {key} and {{key}} (HTML-escaped) placeholders of a template
randomStringA random string of a given length, from a named or custom character set
nonceStringA random string of 32 letters and digits

Objects and classes ​

ExportWhat it is
AsCast a value to a type, for the type checker only
cloneDeepDeep-copy a value, keeping its classes, collections and circular references
ObjectPathGet, set and test a nested property by its path ('a.b[0].c'); paths through a prototype are refused
getObjectPropertyPathsList the paths of the leaf values of an object
getObjectNestingDepthMeasure how deeply an object nests
isEmptyObjectWhether a value is empty: null, undefined, an empty Map or Set, an object without own properties
objectHash, ObjectHashOptionsHash a value by its content, whatever the order of its keys
sortKeys, SortKeysOptionsCopy a plain object with its keys sorted by a comparison function
sortObject, SortObjectOptionsCopy a plain object with its keys in alphabetical order
objectToMapConvert an object into a map of its properties
objectConstructorThe class of an object
objectPrototypeThe prototype of a class
objectParentConstructor, objectParentConstructorsThe parent class of a class, and all its ancestors
isSubclassOfWhether a class extends another one

Arrays and sets ​

ExportWhat it is
sortArray, SortArrayOptionsSort an array in place by its items or their properties
uniqueArrayRemove duplicates from an array
mergeArray, mergeSet, mergeMapMerge two arrays, sets or maps into a new one
arrayToSet, setToArrayConvert between an array and a set
convertArrayLikeToIterable, convertArrayLikeToStreamIterate or stream the items of a string, a buffer or a typed array
Statistics, LinearRegressionMeans, quantiles, deviations, regressions, distributions and random samples of numbers

Async, process and files ​

ExportWhat it is
delayWait for a number of milliseconds
gracefulExitExit the process once cleanup functions have run
DevNullAccept any arguments and ignore them
isPromise, isPromiseLikeWhether a value is a real promise, or any thenable
isAbortErrorWhether an error is an abort error: named AbortError (AbortController.abort(), fetch) or with the code ABORT_ERR (Node.js)
pathExistsWhether a file or a directory exists
isPathWhether a string has the form of a file system path
Glob, GlobSync, GlobOptionsList the files matching glob patterns
isGlobStringWhether a string is a glob pattern

Encodings, hashes and identifiers ​

ExportWhat it is
md5, sha1, sha256The digest of a string, as a Buffer
MessagePackSerialize values to and from MessagePack bytes or strings (a Map decodes as an object, a Set as an array)
UUID, UUIDTypes, UUIDVersion1Options, UUIDVersion4Options, UUIDVersion6OptionsGenerate (v1, v3, v4, v5, v6), validate, parse and stringify UUIDs
URLBuilder, URLAttributesParse a URL of any scheme into its attributes, or build one from them
hexToUnsigned, unsignedToHexConvert between hexadecimal and unsigned integers
hexToSigned, signedToHexConvert between hexadecimal and signed integers (two's complement)
hexToIEEE754, ieee754ToHexConvert between hexadecimal and IEEE 754 floating-point numbers
IP, IPv4, IPv6, IPBinHex, IPv4Subnet, IPv6Subnet, IPv6ResultValidate, convert and compare IP addresses and CIDR blocks

Type guards ​

ExportWhat it is
isSymbolWhether a value is a symbol
isNativeFunctionWhether a function is implemented natively
isHTMLWhether a string looks like HTML
isXMLWhether a string is a well-formed XML document

Errors ​

The utilities throw standard JavaScript errors, plus one error class of their own; the guards (is*) return false instead of throwing.

ErrorThrown when
RangeErrornew Time() (and the Time comparisons) with a string that is not a date; Time.timezone() with an unknown time zone; randomString() with a negative or fractional length or an empty character set; signedToHex()/unsignedToHex() with a number that is not an integer or does not fit in the width; hexToSigned()/hexToUnsigned() with a value beyond Number.MAX_SAFE_INTEGER; hexToIEEE754() with a number of digits other than 8 or 16; UUID.v1()/UUID.v3()/UUID.v4()/UUID.v5()/UUID.v6() with a buffer that is too small (nothing is written); MessagePack.encode() with a bigint outside the 64-bit ranges; MessagePack.decode() with empty or truncated bytes
SyntaxErrorhexToSigned(), hexToUnsigned(), hexToIEEE754() with a character that is not a hexadecimal digit, or with no digits
TypeErrorUUID.parse(), UUID.version(), UUID.v3()/UUID.v5() with an invalid UUID; sortKeys()/sortObject() with a value that is not a plain object or an array; sortArray() by a property, when an item is null or undefined; ObjectPath.set() through a null value, or with a path through __proto__, constructor or prototype; templating() with a template that is not a string
MissingValueErrortemplating() with a placeholder that has no value, unless ignoreMissing is set
ErrorobjectHash() with undefined, an unknown algorithm or encoding, or an unsupported type (a Promise) without ignoreUnknown; MessagePack.encode() with a function, a symbol, a Map key that is neither a string nor a number, or more than 100 levels of nesting; the Statistics methods with an array that is empty or too short

The typings of each helper list the errors it throws.

See also ​

  • doc/en/HelperFunctions.md and doc/en/Time.md in the lakutata package (Chinese versions in doc/zh): the guides to the helpers and to Time.
  • @lakutata/core (lakutata): Application, components and dependency injection; it re-exports Time.
  • @lakutata/security (lakutata/security): passwords, signatures and encryption, for hashes used as secrets.

Released under the MIT License.