metautil
Version:
Metarhia utilities
1,047 lines (915 loc) • 26.1 kB
JavaScript
// error.js
class Error extends globalThis.Error {
constructor(message, options = {}) {
super(message);
const hasOptions = typeof options === 'object';
const { code, cause } = hasOptions ? options : { code: options };
this.code = code;
this.cause = cause;
}
}
class DomainError extends Error {
constructor(code, options = {}) {
const hasCode = typeof code !== 'object';
const opt = hasCode ? { ...options, code } : code;
super('Domain error', opt);
}
toError(errors) {
const { code, cause } = this;
const message = errors[this.code] || this.message;
return new Error(message, { code, cause });
}
}
const isError = (err) => err?.constructor?.name?.includes('Error') || false;
// strings.js
const replace = (str, substr, newstr) => {
if (substr === '') return str;
let src = str;
let res = '';
do {
const index = src.indexOf(substr);
if (index === -1) return res + src;
const start = src.substring(0, index);
src = src.substring(index + substr.length, src.length);
res += start + newstr;
} while (true);
};
const between = (s, prefix, suffix) => {
let i = s.indexOf(prefix);
if (i === -1) return '';
s = s.substring(i + prefix.length);
if (suffix) {
i = s.indexOf(suffix);
if (i === -1) return '';
s = s.substring(0, i);
}
return s;
};
const split = (s, separator) => {
const i = s.indexOf(separator);
if (i < 0) return [s, ''];
return [s.slice(0, i), s.slice(i + separator.length)];
};
const inRange = (x, min, max) => x >= min && x <= max;
const isFirstUpper = (s) => !!s && inRange(s[0], 'A', 'Z');
const isFirstLower = (s) => !!s && inRange(s[0], 'a', 'z');
const isFirstLetter = (s) => isFirstUpper(s) || isFirstLower(s);
const toLowerCamel = (s) => s.charAt(0).toLowerCase() + s.slice(1);
const toUpperCamel = (s) => s.charAt(0).toUpperCase() + s.slice(1);
const toLower = (s) => s.toLowerCase();
const toCamel = (separator) => (s) => {
const words = s.split(separator);
const first = words.length > 0 ? words.shift().toLowerCase() : '';
return first + words.map(toLower).map(toUpperCamel).join('');
};
const spinalToCamel = toCamel('-');
const snakeToCamel = toCamel('_');
const isConstant = (s) => s === s.toUpperCase();
const fileExt = (fileName) => {
const dot = fileName.lastIndexOf('.');
const slash = fileName.lastIndexOf('/');
if (slash > dot) return '';
return fileName.substring(dot + 1, fileName.length).toLowerCase();
};
const trimLines = (s) => {
const chunks = s.split('\n').map((d) => d.trim());
return chunks.filter((d) => d !== '').join('\n');
};
// array.js
const sample = (array, random = Math.random) => {
const index = Math.floor(random() * array.length);
return array[index];
};
const shuffle = (array, random = Math.random) => {
// Based on the algorithm described here:
// https://en.wikipedia.org/wiki/Fisher-Yates_shuffle
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
const projection = (source, fields) => {
const entries = [];
for (const key of fields) {
if (Object.hasOwn(source, key)) {
const value = source[key];
entries.push([key, value]);
}
}
return Object.fromEntries(entries);
};
// async.js
const toBool = [() => true, () => false];
const timeout = (msec, signal = null) =>
new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Timeout of ${msec}ms reached`, 'ETIMEOUT'));
}, msec);
if (!signal) return;
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('Timeout aborted'));
});
});
const delay = (msec, signal = null) =>
new Promise((resolve, reject) => {
const timer = setTimeout(resolve, msec);
if (!signal) return;
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('Delay aborted'));
});
});
const timeoutify = (promise, msec) =>
new Promise((resolve, reject) => {
let timer = setTimeout(() => {
timer = null;
reject(new Error(`Timeout of ${msec}ms reached`, 'ETIMEOUT'));
}, msec);
promise.then(resolve, reject).finally(() => {
if (timer) clearTimeout(timer);
});
});
// datetime.js
const DURATION_UNITS = {
d: 86400, // days
h: 3600, // hours
m: 60, // minutes
s: 1, // seconds
};
const duration = (s) => {
if (typeof s === 'number') return s;
if (typeof s !== 'string') return 0;
let result = 0;
const parts = s.split(' ');
for (const part of parts) {
const unit = part.slice(-1);
const value = parseInt(part.slice(0, -1));
const mult = DURATION_UNITS[unit];
if (!isNaN(value) && mult) result += value * mult;
}
return result * 1000;
};
const twoDigit = (n) => {
const s = n.toString();
if (n < 10) return '0' + s;
return s;
};
const nowDate = (date) => {
if (!date) date = new Date();
const yyyy = date.getUTCFullYear().toString();
const mm = twoDigit(date.getUTCMonth() + 1);
const dd = twoDigit(date.getUTCDate());
return `${yyyy}-${mm}-${dd}`;
};
const nowDateTimeUTC = (date, timeSep = ':') => {
if (!date) date = new Date();
const yyyy = date.getUTCFullYear().toString();
const mm = twoDigit(date.getUTCMonth() + 1);
const dd = twoDigit(date.getUTCDate());
const hh = twoDigit(date.getUTCHours());
const min = twoDigit(date.getUTCMinutes());
const ss = twoDigit(date.getUTCSeconds());
return `${yyyy}-${mm}-${dd}T${hh}${timeSep}${min}${timeSep}${ss}`;
};
const MONTHS = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const NAME_LEN = 3;
const parseMonth = (s) => {
const name = s.substring(0, NAME_LEN);
const i = MONTHS.indexOf(name);
return i >= 0 ? i + 1 : -1;
};
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const parseDay = (s) => {
const name = s.substring(0, NAME_LEN);
const i = DAYS.indexOf(name);
return i >= 0 ? i + 1 : -1;
};
const ORDINAL = ['st', 'nd', 'rd', 'th'];
const isOrdinal = (s) => ORDINAL.some((d) => s.endsWith(d));
const YEAR_LEN = 4;
const parseEvery = (s = '') => {
let YY = -1;
let MM = -1;
let DD = -1;
let wd = -1;
let hh = -1;
let mm = -1;
let ms = 0;
const parts = s.split(' ');
for (const part of parts) {
if (part.includes(':')) {
const [h, m] = split(part, ':');
if (h !== '') hh = parseInt(h);
mm = m === '' ? 0 : parseInt(m);
continue;
}
if (isOrdinal(part)) {
DD = parseInt(part);
continue;
}
if (part.length === YEAR_LEN) {
YY = parseInt(part);
continue;
}
if (MM === -1) {
MM = parseMonth(part);
if (MM > -1) continue;
}
if (wd === -1) {
wd = parseDay(part);
if (wd > -1) continue;
}
const unit = part.slice(-1);
const mult = DURATION_UNITS[unit];
if (typeof mult === 'number') {
const value = parseInt(part);
if (!isNaN(value)) ms += value * mult;
}
}
return { YY, MM, DD, wd, hh, mm, ms: ms > 0 ? ms * 1000 : -1 };
};
const nextEvent = (ev, d = new Date()) => {
let ms = 0;
const Y = d.getUTCFullYear();
const M = d.getUTCMonth() + 1;
const D = d.getUTCDate();
const w = d.getUTCDay() + 1;
const h = d.getUTCHours();
const m = d.getUTCMinutes();
const iY = ev.YY > -1;
const iM = ev.MM > -1;
const iD = ev.DD > -1;
const iw = ev.wd > -1;
const ih = ev.hh > -1;
const im = ev.mm > -1;
const ims = ev.ms > -1;
if (iY && (ev.YY < Y || ev.YY > Y)) return ev.YY < Y ? -1 : 0;
if (iM && (ev.MM < M || ev.MM > M || ev.MM !== M)) return ev.MM < M ? -1 : 0;
if (iD && (ev.DD < D || ev.DD > D || ev.DD !== D)) return ev.DD < D ? -1 : 0;
if (iw && ev.wd !== w) return 0;
if (ih && (ev.hh < h || (ev.hh === h && im && ev.mm < m))) return -1;
if (ih) ms += (ev.hh - h) * DURATION_UNITS.h;
if (im) ms += (ev.mm - m) * DURATION_UNITS.m;
ms *= 1000;
if (ims) ms += ev.ms;
return ms;
};
// objects.js
const makePrivate = (instance) => {
const iface = {};
const fields = Object.keys(instance);
for (const fieldName of fields) {
const field = instance[fieldName];
if (isConstant(fieldName)) {
iface[fieldName] = field;
} else if (typeof field === 'function') {
const bindedMethod = field.bind(instance);
iface[fieldName] = bindedMethod;
instance[fieldName] = bindedMethod;
}
}
return iface;
};
const protect = (allowMixins, ...namespaces) => {
for (const namespace of namespaces) {
const names = Object.keys(namespace);
for (const name of names) {
const target = namespace[name];
if (!allowMixins.includes(name)) Object.freeze(target);
}
}
};
const jsonParse = (buffer) => {
if (buffer.length === 0) return null;
try {
return JSON.parse(buffer);
} catch {
return null;
}
};
const isHashObject = (o) =>
typeof o === 'object' && o !== null && !Array.isArray(o);
const flatObject = (source, fields = []) => {
const target = {};
for (const [key, value] of Object.entries(source)) {
if (!isHashObject(value)) {
target[key] = value;
continue;
}
if (fields.length > 0 && !fields.includes(key)) {
target[key] = { ...value };
continue;
}
for (const [childKey, childValue] of Object.entries(value)) {
const combined = `${key}${toUpperCamel(childKey)}`;
if (source[combined] !== undefined) {
const error = `Can not combine keys: key "${combined}" already exists`;
throw new Error(error);
}
target[combined] = childValue;
}
}
return target;
};
const unflatObject = (source, fields) => {
const result = {};
for (const [key, value] of Object.entries(source)) {
const prefix = fields.find((name) => key.startsWith(name));
if (prefix) {
if (Object.prototype.hasOwnProperty.call(source, prefix)) {
throw new Error(`Can not combine keys: key "${prefix}" already exists`);
}
const newKey = key.substring(prefix.length).toLowerCase();
const section = result[prefix];
if (section) section[newKey] = value;
else result[prefix] = { [newKey]: value };
continue;
}
result[key] = value;
}
return result;
};
const getSignature = (method) => {
const src = method.toString();
const signature = between(src, '({', '})');
if (signature === '') return [];
return signature.split(',').map((s) => s.trim());
};
const namespaceByPath = (namespace, path) => {
const [key, rest] = split(path, '.');
const step = namespace[key];
if (!step) return null;
if (rest === '') return step;
return namespaceByPath(step, rest);
};
const serializeArguments = (fields, args) => {
if (!fields) return '';
const data = {};
for (const par of fields) {
data[par] = args[par];
}
return JSON.stringify(data);
};
const firstKey = (obj) => Object.keys(obj).find(isFirstLetter);
const isInstanceOf = (obj, constrName) => obj?.constructor?.name === constrName;
// collector.js
class Collector {
done = false;
data = {};
keys = [];
count = 0;
exact = true;
reassign = true;
timeout = 0;
defaults = {};
validate = null;
#fulfill = null;
#reject = null;
#cause = null;
#controller = null;
#signal = null;
#timeout = null;
constructor(keys, options = {}) {
const { exact = true, reassign = false } = options;
const { timeout = 0, defaults = {}, validate } = options;
if (validate) this.validate = validate;
this.keys = keys;
if (exact === false) this.exact = false;
if (reassign === false) this.reassign = reassign;
if (typeof defaults === 'object') this.defaults = defaults;
this.#controller = new AbortController();
this.#signal = this.#controller.signal;
if (typeof timeout === 'number' && timeout > 0) {
this.#timeout = AbortSignal.timeout(timeout);
this.#signal = AbortSignal.any([this.#signal, this.#timeout]);
this.#signal.addEventListener('abort', () => {
if (Object.keys(this.defaults).length > 0) this.#default();
if (this.done) return;
this.fail(this.#signal.reason);
});
}
}
#default() {
for (const [key, value] of Object.entries(this.defaults)) {
if (this.data[key] === undefined) this.set(key, value);
}
}
get signal() {
return this.#signal;
}
set(key, value) {
if (this.done) return;
const expected = this.keys.includes(key);
if (!expected && this.exact) {
this.fail(new Error('Unexpected key: ' + key));
return;
}
const has = this.data[key] !== undefined;
if (has && !this.reassign) {
const error = new Error('Collector reassign mode is off');
return void this.fail(error);
}
if (!has && expected) this.count++;
this.data[key] = value;
if (this.count === this.keys.length) {
this.done = true;
this.#timeout = null;
if (this.#fulfill) this.#fulfill(this.data);
}
}
take(key, fn, ...args) {
fn(...args, (err, data) => {
if (err) this.fail(err);
else this.set(key, data);
});
}
wait(key, fn, ...args) {
const promise = fn instanceof Promise ? fn : fn(...args);
promise.then(
(data) => this.set(key, data),
(err) => this.fail(err),
);
}
collect(sources) {
for (const [key, collector] of Object.entries(sources)) {
collector.then(
(data) => this.set(key, data),
(err) => this.fail(err),
);
}
}
fail(error) {
this.done = true;
this.#timeout = null;
const err = error || new Error('Collector aborted');
this.#cause = err;
this.#controller.abort();
if (this.#reject) this.#reject(err);
}
abort() {
this.fail();
}
then(onFulfilled, onRejected = null) {
return new Promise((resolve, reject) => {
this.#fulfill = resolve;
this.#reject = reject;
if (!this.done) return;
if (this.validate) {
try {
this.validate(this.data);
} catch (err) {
this.#cause = err;
}
}
if (this.#cause) reject(this.#cause);
else resolve(this.data);
}).then(onFulfilled, onRejected);
}
}
const collect = (keys, options) => new Collector(keys, options);
// events.js
const DONE = { done: true, value: undefined };
class EventIterator {
#resolvers = [];
#emitter = null;
#eventName = '';
#listener = null;
#onerror = null;
#done = false;
constructor(emitter, eventName) {
this.#emitter = emitter;
this.#eventName = eventName;
this.#listener = (value) => {
for (const resolver of this.#resolvers) {
resolver.resolve({ done: this.#done, value });
}
};
emitter.on(eventName, this.#listener);
this.#onerror = (error) => {
for (const resolver of this.#resolvers) {
resolver.reject(error);
}
this.#finalize();
};
emitter.on('error', this.#onerror);
}
next() {
return new Promise((resolve, reject) => {
if (this.#done) return void resolve(DONE);
this.#resolvers.push({ resolve, reject });
});
}
#finalize() {
if (this.#done) return;
this.#done = true;
this.#emitter.off(this.#eventName, this.#listener);
this.#emitter.off('error', this.#onerror);
for (const resolver of this.#resolvers) {
resolver.resolve(DONE);
}
this.#resolvers.length = 0;
}
async return() {
this.#finalize();
return DONE;
}
async throw() {
this.#finalize();
return DONE;
}
}
class EventIterable {
#emitter = null;
#eventName = '';
constructor(emitter, eventName) {
this.#emitter = emitter;
this.#eventName = eventName;
}
[Symbol.asyncIterator]() {
return new EventIterator(this.#emitter, this.#eventName);
}
}
class Emitter {
#events = new Map();
#maxListeners = 10;
constructor(options = {}) {
this.#maxListeners = options.maxListeners ?? 10;
}
emit(eventName, value) {
const event = this.#events.get(eventName);
if (!event) {
if (eventName !== 'error') return Promise.resolve();
throw new Error('Unhandled error');
}
const on = event.on.slice();
const promises = on.map(async (fn) => fn(value));
if (event.once.size > 0) {
const len = event.on.length;
const on = new Array(len);
let index = 0;
for (let i = 0; i < len; i++) {
const listener = event.on[i];
if (!event.once.has(listener)) on[index++] = listener;
}
if (index === 0) {
this.#events.delete(eventName);
return Promise.resolve();
}
on.length = index;
this.#events.set(eventName, { on, once: new Set() });
}
return Promise.all(promises).then(() => undefined);
}
#addListener(eventName, listener, once) {
let event = this.#events.get(eventName);
if (!event) {
const on = [listener];
event = { on, once: once ? new Set(on) : new Set() };
this.#events.set(eventName, event);
} else {
if (event.on.includes(listener)) {
throw new Error('Duplicate listeners detected');
}
event.on.push(listener);
if (once) event.once.add(listener);
}
if (event.on.length > this.#maxListeners) {
throw new Error(
`MaxListenersExceededWarning: Possible memory leak. ` +
`Current maxListeners is ${this.#maxListeners}.`,
);
}
}
on(eventName, listener) {
this.#addListener(eventName, listener, false);
}
once(eventName, listener) {
this.#addListener(eventName, listener, true);
}
off(eventName, listener) {
if (!listener) return void this.#events.delete(eventName);
const event = this.#events.get(eventName);
if (!event) return;
const index = event.on.indexOf(listener);
if (index > -1) event.on.splice(index, 1);
event.once.delete(listener);
}
toPromise(eventName) {
return new Promise((resolve) => {
this.once(eventName, resolve);
});
}
toAsyncIterable(eventName) {
return new EventIterable(this, eventName);
}
clear(eventName) {
if (!eventName) return void this.#events.clear();
this.#events.delete(eventName);
}
listeners(eventName) {
if (!eventName) throw new Error('Expected eventName');
const event = this.#events.get(eventName);
return event ? event.on : [];
}
listenerCount(eventName) {
if (!eventName) throw new Error('Expected eventName');
const event = this.#events.get(eventName);
return event ? event.on.length : 0;
}
eventNames() {
return Array.from(this.#events.keys());
}
}
// http.js
const parseHost = (host) => {
if (!host) return 'no-host-name-in-http-headers';
const portOffset = host.indexOf(':');
if (portOffset > -1) host = host.substr(0, portOffset);
return host;
};
const parseParams = (params) => Object.fromEntries(new URLSearchParams(params));
const parseCookies = (cookie) => {
const values = [];
const items = cookie.split(';');
for (const item of items) {
const [key, val = ''] = item.split('=');
values.push([key.trim(), val.trim()]);
}
return Object.fromEntries(values);
};
const parseRange = (range) => {
if (!range || !range.includes('=')) return {};
const bytes = range.split('=').pop();
if (!bytes || !range.includes('-')) return {};
const [start, end] = bytes.split('-').map((n) => parseInt(n));
if (isNaN(start)) return isNaN(end) ? {} : { tail: end };
return isNaN(end) ? { start } : { start, end };
};
// pool.js
class Pool {
constructor(options = {}) {
this.items = [];
this.free = [];
this.queue = [];
this.timeout = options.timeout || 0;
this.current = 0;
this.size = 0;
this.available = 0;
}
async next(exclusive = false) {
if (this.size === 0) return null;
if (this.available === 0) {
return new Promise((resolve, reject) => {
const waiting = { resolve, timer: null };
waiting.timer = setTimeout(() => {
waiting.resolve = null;
this.queue.shift();
reject(new Error('Pool next item timeout'));
}, this.timeout);
this.queue.push(waiting);
});
}
let item = null;
let free = false;
do {
item = this.items[this.current];
free = this.free[this.current];
this.current++;
if (this.current === this.size) this.current = 0;
} while (!item || !free);
if (exclusive) {
const index = this.items.indexOf(item);
this.free[index] = false;
this.available--;
}
return item;
}
add(item) {
if (this.items.includes(item)) throw new Error('Pool: add duplicates');
this.size++;
this.available++;
this.items.push(item);
this.free.push(true);
}
async capture() {
const item = await this.next(true);
return item;
}
release(item) {
const index = this.items.indexOf(item);
if (index < 0) throw new Error('Pool: release unexpected item');
if (this.free[index]) throw new Error('Pool: release not captured');
if (this.queue.length > 0) {
const { resolve, timer } = this.queue.shift();
clearTimeout(timer);
if (resolve) return void setTimeout(resolve, 0, item);
}
this.free[index] = true;
this.available++;
}
isFree(item) {
const index = this.items.indexOf(item);
if (index < 0) return false;
return this.free[index];
}
}
// semaphore.js
class Semaphore {
constructor({ concurrency, size = 0, timeout = 0 }) {
this.concurrency = concurrency;
this.counter = concurrency;
this.timeout = timeout;
this.size = size;
this.queue = [];
this.empty = true;
}
async enter() {
return new Promise((resolve, reject) => {
if (this.counter > 0) {
this.counter--;
this.empty = false;
return void resolve();
}
if (this.queue.length >= this.size) {
return void reject(new Error('Semaphore queue is full'));
}
const waiting = { resolve, timer: null };
waiting.timer = setTimeout(() => {
waiting.resolve = null;
this.queue.shift();
const { counter, concurrency } = this;
this.empty = this.queue.length === 0 && counter === concurrency;
reject(new Error('Semaphore timeout'));
}, this.timeout);
this.queue.push(waiting);
this.empty = false;
});
}
leave() {
if (this.queue.length === 0) {
this.counter++;
this.empty = this.counter === this.concurrency;
return;
}
const { resolve, timer } = this.queue.shift();
clearTimeout(timer);
if (resolve) setTimeout(resolve, 0);
const { counter, concurrency } = this;
this.empty = this.queue.length === 0 && counter === concurrency;
}
}
// units.js
const SIZE_UNITS = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const bytesToSize = (bytes) => {
if (bytes === 0) return '0';
const exp = Math.floor(Math.log(bytes) / Math.log(1000));
const size = bytes / 1000 ** exp;
const short = Math.round(size);
const unit = exp === 0 ? '' : ' ' + SIZE_UNITS[exp - 1];
return short.toString() + unit;
};
const UNIT_SIZES = {
yb: 24, // yottabyte
zb: 21, // zettabyte
eb: 18, // exabyte
pb: 15, // petabyte
tb: 12, // terabyte
gb: 9, // gigabyte
mb: 6, // megabyte
kb: 3, // kilobyte
};
const sizeToBytes = (size) => {
const length = size.length;
const unit = size.substring(length - 2, length).toLowerCase();
const value = parseInt(size, 10);
const exp = UNIT_SIZES[unit];
if (!exp) return value;
return value * Math.pow(10, exp);
};
// browser.js
const UINT32_MAX = 0xffffffff;
const BUF_LEN = 1024;
const BUF_SIZE = BUF_LEN * Uint32Array.BYTES_PER_ELEMENT;
const randomPrefetcher = {
buf: new Uint8Array(BUF_SIZE),
view: null,
pos: 0,
next() {
const { buf, view, pos } = this;
let start = pos;
if (start === buf.length) {
start = 0;
crypto.getRandomValues(buf);
}
const rnd = view.getUint32(start, true) / (UINT32_MAX + 1);
this.pos = start + Uint32Array.BYTES_PER_ELEMENT;
return rnd;
},
};
crypto.getRandomValues(randomPrefetcher.buf);
randomPrefetcher.view = new DataView(
randomPrefetcher.buf.buffer,
randomPrefetcher.buf.byteOffset,
randomPrefetcher.buf.byteLength,
);
const cryptoRandom = (min, max) => {
const rnd = randomPrefetcher.next();
if (min === undefined) return rnd;
const [a, b] = max === undefined ? [0, min] : [min, max];
return a + Math.floor(rnd * (b - a + 1));
};
const random = (min, max) => {
const rnd = Math.random();
if (min === undefined) return rnd;
const [a, b] = max === undefined ? [0, min] : [min, max];
return a + Math.floor(rnd * (b - a + 1));
};
const generateUUID = () => crypto.randomUUID();
const generateKey = (possible, length) => {
if (length < 0) return '';
const base = possible.length;
if (base < 1) return '';
const key = new Uint8Array(length);
const randomValues = new Uint8Array(length);
crypto.getRandomValues(randomValues);
for (let i = 0; i < length; i++) {
const index = randomValues[i] % base;
key[i] = possible.charCodeAt(index);
}
return String.fromCharCode.apply(null, key);
};
export {
Error,
DomainError,
isError,
replace,
between,
split,
isFirstUpper,
isFirstLower,
isFirstLetter,
toLowerCamel,
toUpperCamel,
toLower,
toCamel,
spinalToCamel,
snakeToCamel,
isConstant,
fileExt,
trimLines,
sample,
shuffle,
projection,
toBool,
timeout,
delay,
timeoutify,
duration,
nowDate,
nowDateTimeUTC,
parseMonth,
parseDay,
parseEvery,
nextEvent,
makePrivate,
protect,
jsonParse,
isHashObject,
flatObject,
unflatObject,
getSignature,
namespaceByPath,
serializeArguments,
firstKey,
isInstanceOf,
Collector,
collect,
Emitter,
parseHost,
parseParams,
parseCookies,
parseRange,
Pool,
Semaphore,
bytesToSize,
sizeToBytes,
cryptoRandom,
random,
generateUUID,
generateKey,
};