js-awe
Version:
Awesome js utils including - plan: An Asynchronous control flow with a functional taste - Chrono: record and visualize timelines in the console
1,209 lines (1,208 loc) • 76.4 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _EnumMap_instances, _EnumMap_validateAndTransform;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fillWith = exports.memoize = exports.pushAt = exports.pushUniqueKeyOrChange = exports.pushUniqueKey = exports.transition = exports.Enum = exports.EnumMap = exports.copyPropsWithValueUsingRules = exports.copyPropsWithValue = exports.project = exports.traverseVertically = exports.traverse = exports.removeDuplicates = exports.arrayOfObjectsToObject = exports.arrayToObject = exports.notTo = exports.sleepWithFunction = exports.sleepWithValue = exports.sleep = exports.isPromise = exports.arraySorter = exports.filterFlatMap = exports.defaultValue = exports.findIndexOrNextInSortedArray = exports.findIndexOrPreviousInSortedArray = exports.findIndexInSortedArray = exports.sorterByFields = exports.sorterByPaths = exports.stripDollarRoot = exports.pathReplacingArrayIndexWithAsterisk = exports.setAt = exports.getAt = exports.deepFreeze = exports.findDeepKey = exports.colorByStatus = exports.colorMessageByStatus = exports.colorMessage = exports.colors = exports.indexOfNthMatch = exports.urlDecompose = exports.urlCompose = exports.isBasicType = exports.createCustomErrorClass = exports.CustomError = exports.summarizeError = exports.queryObjToStr = exports.varSubsDoubleBracket = exports.firstCapital = exports.logWithPrefix = void 0;
exports.processExit = exports.retryWithSleep = exports.loopIndexGenerator = exports.oneIn = exports.repeat = exports.cleanString = exports.replaceAll = exports.setDateToMidnight = exports.isDateMidnight = exports.getSameDateOrPreviousFridayForWeekends = exports.dayOfWeek = exports.nextDayOfWeek = exports.previousDayOfWeek = exports.addDays = exports.subtractDays = exports.diffInDaysYYYY_MM_DD = exports.dateToObj = exports.YYYY_MM_DD_hh_mm_ss_ToUtcDate = exports.dateFormatter = exports.MONTHS = exports.DAYS = exports.formatDate = exports.isStringADate = exports.isALeaf = exports.isEmpty = exports.isDate = exports.numberToFixedString = void 0;
const just_clone_1 = __importDefault(require("just-clone"));
const jsonpath_plus_1 = require("jsonpath-plus");
// biome-ignore lint/style/useNodejsImportProtocol: <explanation>
const path_1 = require("path");
const logWithPrefix = (title, displayFunc) => (message) => {
let finalMessage = message;
if (typeof displayFunc === 'function') {
finalMessage = displayFunc(message);
}
console.log(`${title}: ${finalMessage}`);
return message;
};
exports.logWithPrefix = logWithPrefix;
let processCwd;
function transformStackTraceLine(line) {
const processCwd = process.cwd();
let starPathPos = line.indexOf('(file://', '');
if (starPathPos === -1)
starPathPos = line.indexOf('file://', '');
if (starPathPos === -1)
starPathPos = line.indexOf('(', '');
if (starPathPos === -1)
return line;
const cleanLine = line
.replace('(file://', '')
.replace('file://', '')
.replace('(', '')
.replace(')', '');
const resultPositionMatch = cleanLine.match(/[0-9]+:[0-9]+/);
if (resultPositionMatch === null)
return line;
const position = resultPositionMatch[0];
const filePath = cleanLine.substring(starPathPos, resultPositionMatch.index - 1);
const relativePath = (0, path_1.relative)(processCwd, filePath);
return `${line.substring(0, starPathPos - 1)} ${relativePath}:${position}`;
}
function summarizeError(error, maxStackTraces = 5) {
var _a;
if (error instanceof Error === false)
return 'Not an error object';
const stackTraceLines = error.stack.split('\n');
const filteredStackTrace = stackTraceLines
.filter(line => !line.includes('node_modules') && !line.includes('node:internal'))
.map(line => line.trim())
.filter(line => line.startsWith('at'))
.map(transformStackTraceLine);
const condensedStackTrace = [];
const totalTraces = filteredStackTrace.length;
if (totalTraces > maxStackTraces) {
condensedStackTrace.push(...filteredStackTrace.slice(0, maxStackTraces - 1));
condensedStackTrace.push('...skipped...');
condensedStackTrace.push(filteredStackTrace[totalTraces - 1]);
}
else {
condensedStackTrace.push(...filteredStackTrace);
}
const condensedStackTraceString = condensedStackTrace.join(' -> ');
const ErrorString = `${error.name}: ${error.message} ${((_a = error.cause) === null || _a === void 0 ? void 0 : _a.message) ? `[cause]: ${error.cause.message}` : ''}`;
return `${ErrorString}\nStack Trace: ${condensedStackTraceString}`;
}
exports.summarizeError = summarizeError;
// function a(){b()}
// function b(){c()}
// function c(){d()}
// function d(){e()}
// function e(){f()}
// function f(){g()}
// function g(){throw new Error('This is an error')}
// try{
// a()
// }catch(e)
// {
// console.log(e)
// console.log(summarizeError(e))
// }
class CustomError extends Error {
constructor(name = 'GENERIC', message = name, data = { status: 500 }) {
super(message);
super.name = name;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
this.data = data;
}
map(func) {
return this;
}
chain(func) {
return this;
}
summarizeError(error) {
return summarizeError(error);
}
}
exports.CustomError = CustomError;
CustomError.of = CustomError;
// try {
// throw new CustomError('aa','bb',{a:1,b:2})
// }catch(e)
// {
// console.log(`name: ${e.name}, message: ${e.message}, data: ${JSON.stringify(e.data)}, stack: ${e.stack}`)
// }
//
// mapping a function to CustomError should return the CustomError without exeecuting the function
// import { pipeWithChain, R } from './ramdaExt.js'
// let divide = (dividend, divisor) =>
// divisor !== 0
// ? dividend/divisor
// : new CustomError('ZERO_DIVISION_EXC','Division by zero',{dividend,divisor})
// R.map(a => {
// console.log(`It shouldn't print this`)
// return a +2
// })(divide(8,0)) //?
function createCustomErrorClass(errorName) {
const errorClass = class extends CustomError {
constructor(name, message, data) {
super(name, message, data);
this.name = errorName;
}
};
return errorClass;
}
exports.createCustomErrorClass = createCustomErrorClass;
function isBasicType(variableToCheck) {
const type = typeof variableToCheck;
return (type !== 'object' && type !== 'undefined' && type !== 'function');
}
exports.isBasicType = isBasicType;
// isBasicType(null) //?
// isBasicType(undefined) //?
// isBasicType(new Map()) //?
// isBasicType({}) //?
// isBasicType(Symbol()) //?
// isBasicType('22') //?
// isBasicType(Number(2)) //?
class Enum {
constructor(values, rules) {
// It will contain the active key for example 'OFF'. Once initialized activeObjectKey[activeKey] should be always equal to true
let activeKey;
let stateRules;
if (Array.isArray(values) === false)
throw new CustomError('NOT_AN_ARRAY', 'Only Array composed of non objects are permitted');
if (values.filter((elem) => elem === 'object').length > 0)
throw new CustomError('ARRAY_VALUES_MUST_BE_OF_BASIC_TYPE', 'Only basic types are allowed');
const valuesNotAllowed = values.filter(elem => elem === 'get' || elem === 'set' || elem === 'getValue');
if (valuesNotAllowed.length > 0) {
throw new CustomError('ENUM_INVALID_ENUM_VALUE', `The following ENUM value/s are not allowed: ${valuesNotAllowed} as they are reserved words for enum`);
}
const valuesWithoutDuplicates = removeDuplicates(values);
// activeObjectKey will be an object with keys from values array and only one current key active: {ON:false,OFF:true}
const activeObjectKey = arrayToObject(valuesWithoutDuplicates, function defaultValue() { return false; });
activeKey = values[0];
activeObjectKey[activeKey] = true;
if (rules !== undefined)
setupRules(rules);
this.get = get;
this.set = set;
this.getValue = getValue;
// biome-ignore lint/correctness/noConstructorReturn: <explanation>
return new Proxy(activeObjectKey, this);
///
function setupRules(rules) {
if (rules === null || typeof rules !== 'object' || Array.isArray(rules) === true) {
throw new CustomError('ENUM_RULES_BAD_FORMAT', `rules is not an object: ${rules}`);
}
for (const elem in rules) {
if (activeObjectKey[elem] === undefined || Array.isArray(rules[elem]) === false) {
throw new CustomError('ENUM_RULES_BAD_FORMAT', `Each attribute of rules must be an element in the ENUM and its value should be an array: ${activeObjectKey[elem]}${rules[elem]}`);
}
const valuesWithProblems = rules[elem].filter(itemTo => activeObjectKey[itemTo] === undefined);
if (valuesWithProblems.length > 0) {
throw new CustomError('ENUM_RULES_BAD_FORMAT', `All elements in a rule entry must be one of the list values in the ENUM. The following values dont exist: ${valuesWithProblems}`);
}
}
stateRules = (0, just_clone_1.default)(rules);
}
function get(_target, prop) {
if (prop === 'getValue') {
return getValue;
}
if (activeObjectKey[prop] == null)
throw new CustomError('ENUM_INVALID_PROPERTY', `.${prop} is none of the possible values ${this}`);
return activeObjectKey[prop];
}
function set(_undefined, prop, value) {
if (value !== true) {
throw new CustomError('ENUM_ACTIVATION_NO_TRUE', `Tryng to set ${prop} with ${value} but activation only admits true`);
}
if (activeObjectKey[prop] === undefined) {
throw new CustomError('ENUM_INVALID_PROPERTY', `.${prop} is none of the possible values ${this}`);
}
if (validateChange(activeKey, prop)) {
activeObjectKey[activeKey] = false;
activeObjectKey[prop] = true;
activeKey = prop;
}
else
throw new CustomError('ENUM_TRANSITION_NOT_ALLOWED', `.From: ${activeKey} --> To: ${prop}`);
return true;
}
function validateChange(activeElement, prop) {
if (stateRules === undefined)
return true;
return stateRules[activeElement] !== undefined && stateRules[activeElement].indexOf(prop) !== -1;
}
function getValue() {
return activeKey;
}
}
}
exports.Enum = Enum;
// const ENGINE = new Enum(
// ['UNDEFINED', 'START', 'SPEED', 'BREAK', 'STOP','ABANDONED'],
// {
// 'UNDEFINED':['START'],
// 'START':['SPEED', 'BREAK', 'STOP'],
// 'SPEED':['BREAK', 'STOP'],
// }
// )
// ENGINE.START = true
// ENGINE.START //?
// ENGINE.SPEED = true
// ENGINE.SPEED
// try {
// ENGINE.SPEED = true
// }catch(e)
// {
// e //? ENUM_TRANSITION_NOT_ALLOWED It is not defined to go to itself.
// }
// try {
// const result = new Enum(
// ['UNDEFINED', 'START', 'SPEED', 'BREAK', 'STOP','ABANDONED'],
// {
// 'UNDEFINED':['START'],
// 'START':['SPEED', 'BREAK', 'STOP'],
// 'SPEEDRT':['BREAK', 'STOP'],
// }
// )
// } catch (e) {
// e //? ENUM_RULES_BAD_FORMAT SPEEDRT is not valid
// }
class EnumMap {
constructor(values) {
_EnumMap_instances.add(this);
const objLiteral = __classPrivateFieldGet(this, _EnumMap_instances, "m", _EnumMap_validateAndTransform).call(this, (0, just_clone_1.default)(values));
// biome-ignore lint/correctness/noConstructorReturn: <explanation>
return new Proxy(objLiteral, this);
}
get(target, prop) {
if (target[prop] == null && this[prop] == null)
throw new CustomError('ENUM_OUT_OF_RANGE', `.${prop} is none of the possible values ${this}`);
if (this[prop] != null)
return this[prop];
return target[prop];
}
set(_undefined, prop) {
throw new CustomError('ENUM_NOT_MODIFIABLE', `Object of .${prop} is not modifiable`);
}
invert() {
const invertedValues = {};
for (const elem in this) {
// biome-ignore lint/suspicious/noPrototypeBuiltins: <explanation>
if (this.hasOwnProperty(elem)) {
if (isBasicType(this[elem]) === false)
throw new CustomError('INVERT_VALUES_NOT_BASIC_TYPE', 'EnumMap values should be basic types');
invertedValues[this[elem]] = elem;
}
}
return new EnumMap((0, just_clone_1.default)(invertedValues));
}
}
exports.EnumMap = EnumMap;
_EnumMap_instances = new WeakSet(), _EnumMap_validateAndTransform = function _EnumMap_validateAndTransform(values) {
if (values === undefined || values === null)
throw new Error('Null or undefined is not permitted to construct a EnumMap instance.');
const valuesProtoName = Object.getPrototypeOf(values).constructor.name;
if (valuesProtoName === 'Map') {
return Object.fromEntries(values);
}
if (valuesProtoName === 'Object') {
return values;
}
let typeOfValue;
const objectResult = [];
if (valuesProtoName === 'Array') {
for (let i = 0; i < values.length; i++) {
// basicTypes: ['SUNDAY', 'MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY']
if (isBasicType(values[i])) {
objectResult[values[i]] = i;
if (typeOfValue !== undefined && typeOfValue !== 'basicType')
throw new CustomError('ENUMMAP_VALUES_NOT_VALID', 'EnumMap values should be consistent...');
typeOfValue = 'basicType';
}
// elements are [key, value]: [['SUNDAY',1],['MONDAY',5], ['TUESDAY',2],['WEDNESDAY',3],['THURSDAY',9],['FRIDAY',6],['SATURDAY',4]]
if (Array.isArray(values[i]) && values[i].length === 2) {
objectResult[values[i][0]] = values[i][1];
if (typeOfValue !== undefined && typeOfValue !== 'array')
throw new CustomError('ENUMMAP_VALUES_NOT_VALID', 'EnumMap values should be consistent...');
typeOfValue = 'array';
}
// elements are object with {key:value} [{SUNDAY:1},{MONDAY:5}, {TUESDAY:2},{WEDNESDAY:3},{THURSDAY:9},{FRIDAY:6},{SATURDAY:4}]
if (values[i] !== null && typeof values[i] === 'object' && Object.keys(values[i]).length === 1) {
const key = Object.keys(values[i])[0];
objectResult[key] = values[i][key];
if (typeOfValue !== undefined && typeOfValue !== 'object')
throw new CustomError('ENUMMAP_VALUES_NOT_VALID', 'EnumMap values should be consistent...');
typeOfValue = 'object';
}
}
if (typeOfValue === undefined) {
throw new CustomError('ENUM_VALUES_NOT_VALID', 'Values must be an array of strings, an array of arrays, an array of objects or an array of maps');
}
}
return objectResult;
};
// {
// const SWITCHER = new EnumMap({ON:0,OFF:1})
// SWITCHER.ON //? 0
// SWITCHER.OFF //? 1
// try { SWITCHER.ONFF
// }catch(e){
// e // ENUM_OUT_OF_RANGE
// }
// const INVERT_SWITCHER = SWITCHER.invert()
// INVERT_SWITCHER['0'] //?
// const value = SWITCHER.ON
// value === 0 //? true
// }
function transition(states, events, transitions) {
states.forEach(validateStateFormat);
events.forEach(validateEventFormat);
let state = states[0];
const finalTransitions = Object.entries(transitions).reduce((acum, [stateKey, stateValue]) => {
// validations
validateState(stateKey);
let newStateValue = stateValue;
if (typeof stateValue === 'string') {
validateState(stateValue);
newStateValue = events.reduce((acum, current) => {
acum[current] = stateValue;
return acum;
}, {});
}
else {
for (const [key, value] of Object.entries(newStateValue)) {
validateEvent(key);
validateState(value);
}
}
acum[stateKey] = Object.assign(Object.assign({}, acum[stateKey]), newStateValue);
return acum;
}, states.reduce((acum, current) => {
acum[current] =
events.reduce((acum2, el2) => {
acum2[el2] = el2.toUpperCase();
return acum2;
}, {});
return acum;
}, {}));
function sendEvent(event) {
validateEvent(event);
state = finalTransitions[state][event];
return state;
}
sendEvent.valueOf = () => state;
return sendEvent;
function validateStateFormat(state) {
if (state !== state.toUpperCase())
throw new CustomError('STATE_MUST_BE_UPPERCASE', `The state: ${state} does not have all characters in uppercase`);
}
function validateState(state) {
if (states.some(el => el === state) === false)
throw new CustomError('STATE_NOT_FOUND', `The state: ${state} was not found in the list of states supplied: ${states}`);
}
function validateEventFormat(event) {
if (event !== event.toLowerCase())
throw new CustomError('EVENT_MUST_BE_LOWERCASE', `The event: ${event} does not have all characters in lowercase`);
}
function validateEvent(event) {
if (events.some(el => el === event) === false)
throw new CustomError('EVENT_NOT_FOUND', `The event: ${event} was not found in the list of events supplied: ${events}`);
}
}
exports.transition = transition;
// const tranDef = [
// ['SYNC', 'PROMISE', 'FUTURE', 'PROMISE_AND_FUTURE'],
// ['sync','promise','future'],
// // STATE:{event:NEW_STATE}
// // if a event is not defined within a STATE then the default value is selected STATE:{missing_event: NEW_STATE(missing_event.toUpperCase())}
// {
// PROMISE:{
// sync:'PROMISE',
// future: 'PROMISE_AND_FUTURE'
// //by default: promise: 'PROMISE'
// },
// FUTURE:{
// sync:'FUTURE',
// promise: 'PROMISE_AND_FUTURE',
// },
// PROMISE_AND_FUTURE: 'PROMISE_AND_FUTURE' // same as {sync: 'PROMISE_AND_FUTURE', promise: 'PROMISE_AND_FUTURE', future: 'PROMISE_AND_FUTURE'}
// }
// ]
// const typeOfList = transition(...tranDef)
// typeOfList('future') //?
// typeOfList('future') //?
// typeOfList('promise') //?
// typeOfList('sync') //?
// try{
// typeOfList('sync2') //?
// }catch(e) {console.log(e)}
// typeOfList('sync') //?
// typeOfList.valueOf() //?
function arrayToObject(arr, defaultValueFunction) {
return arr.reduce((acum, current, index) => {
acum[current] = defaultValueFunction(current, index);
return acum;
}, {});
}
exports.arrayToObject = arrayToObject;
function arrayOfObjectsToObject(iterable) {
if (typeof (iterable === null || iterable === void 0 ? void 0 : iterable[Symbol.iterator]) === 'function') {
const acum = {};
for (const elem of iterable) {
for (const key in elem) {
acum[key] = elem[key];
}
}
return acum;
}
return arr.reduce((acum, current, index) => {
for (const key in current) {
acum[key] = current[key];
}
return acum;
}, {});
}
exports.arrayOfObjectsToObject = arrayOfObjectsToObject;
function removeDuplicates(arr) {
return [...new Set(arr)];
}
exports.removeDuplicates = removeDuplicates;
function reviverPromiseForCloneDeep(value) {
if (jsUtils.isPromise(value))
return value;
}
// reviver is called for each node as: reviver(nodeRef, currentPath, parent, key).
// For example: being objIni={a:{b:{c:3}},d:4} the reviver to call node a.b will be
// reviver({c:3}, ['a','b'], {b:{c:3}}, 'b') currentPath=['$', 'parent', 'son', '0', 'jose']
// reviver return value will impact traverse:
// undefined: do nothing.
// Any value: assign this value (parent[key])
// traverse.stop: stop inmediatly traverse
// traverse.skip: skip node. It doesnt look at children of node.
// prune: if true remove node.
function traverse(objIni, reviver, pureFunction = true) {
const currentPath = ['$'];
const objClone = pureFunction ? (0, just_clone_1.default)(objIni, reviverPromiseForCloneDeep) : objIni;
let exitVar = false;
const objForReviver = {};
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
objForReviver['$'] = objClone;
const isSkipNodeOnce = reviverProcess(reviver, objForReviver, '$', currentPath);
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
if (objClone !== objForReviver['$'])
return objForReviver['$'];
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
if (exitVar === true)
return objForReviver['$'];
if (isSkipNodeOnce === false) {
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
traverseRec(objForReviver['$']);
}
// biome-ignore lint/complexity/useLiteralKeys: <explanation>
return objForReviver['$'];
function traverseRec(obj) {
if (obj && obj instanceof Object && exitVar === false) {
for (const prop in obj) {
// biome-ignore lint/suspicious/noPrototypeBuiltins: <explanation>
if (obj.hasOwnProperty(prop)) {
currentPath.push(prop);
const isSkipNodeOnce = reviverProcess(reviver, obj, prop, currentPath);
if (exitVar === true)
return;
if (isSkipNodeOnce === false) {
traverseRec(obj[prop]);
}
currentPath.pop();
}
}
}
}
function reviverProcess(reviver, obj, prop, currentPath) {
let isSkipNodeOnce = false;
if (reviver) {
const resultReviver = reviver(obj[prop], currentPath, obj, prop);
if (resultReviver !== undefined && resultReviver !== traverse.stop && resultReviver !== traverse.skip && resultReviver !== traverse.delete) {
obj[prop] = resultReviver;
// to avoid infinite loops of reviver. reviver example: changing a string for an object having a string.
// (node)=>{if(typeof node === 'string') return {name:node}}
isSkipNodeOnce = true;
}
if (resultReviver === traverse.stop) {
exitVar = true;
}
if (resultReviver === traverse.skip) {
isSkipNodeOnce = true;
}
if (resultReviver === traverse.delete) {
obj[prop] = undefined;
isSkipNodeOnce = true;
}
}
return isSkipNodeOnce;
}
}
exports.traverse = traverse;
traverse.skip = Symbol();
traverse.stop = Symbol();
traverse.delete = Symbol();
traverse.matchPath = (pathStringQuery, reviverPath) => {
const pathStringArr = pathStringQuery.split('.');
if (pathStringArr.length !== reviverPath.length) {
return false;
}
return pathStringArr.every((el, index) => el === '*' || el === reviverPath[index]);
};
function traverseVertically(functionToRun, verFields, toTraverse) {
var _a, _b, _c, _d;
if (Array.isArray(toTraverse) === false)
return;
let runIndex = 0;
let maxLength = 0;
let firstTime = true;
for (let i = 0; firstTime || i < maxLength; i++) {
const toReturn = [];
for (let j = 0; j < toTraverse.length; j++) {
toReturn[j] = Object.assign({}, toTraverse[j]);
for (const field of verFields) {
if (firstTime) {
maxLength = ((_b = (_a = toTraverse[j]) === null || _a === void 0 ? void 0 : _a[field]) === null || _b === void 0 ? void 0 : _b.length) > maxLength
? toTraverse[j][field].length
: maxLength;
}
toReturn[j][field] = (_d = (_c = toTraverse[j]) === null || _c === void 0 ? void 0 : _c[field]) === null || _d === void 0 ? void 0 : _d[i];
}
}
if (maxLength !== 0) {
functionToRun(toReturn, runIndex);
runIndex++;
}
firstTime = false;
}
}
exports.traverseVertically = traverseVertically;
function project(paths, json, removeWithDelete = true) {
const toDelete = Symbol();
let copy;
if (json === null || json === undefined)
return json;
if (Array.isArray(json))
copy = [];
else if (Object.getPrototypeOf(json) === Object.prototype)
copy = {};
else if (Array.isArray(paths) === false)
throw new Error('paths must be an array');
else if (paths.filter(el => el === '+$').length - paths.filter(el => el === '-$').length > 0)
return json;
else
return undefined;
for (const pathWithSign of paths) {
if (pathWithSign[0] !== '+' && pathWithSign[0] !== '-') {
throw new Error('ivanlid format');
}
const isInclude = pathWithSign[0] === '+';
const path = pathWithSign.substring(1);
const result = (0, jsonpath_plus_1.JSONPath)({ resultType: 'all', path, json });
const pendingToFilter = new Map();
for (const { pointer, value } of result) {
const setAtPath = pointer.substring(1).split('/');
if (setAtPath.length === 1 && setAtPath[0] === '')
copy = isInclude ? (0, just_clone_1.default)(value) : undefined;
else {
if (removeWithDelete === true && isInclude === false) {
const parentPath = setAtPath.slice(0, -1);
const parent = getAt(copy, parentPath);
if (Array.isArray(parent) === true) {
// Arrays are stored in a map to be reviewed later to filter out the items mark for deletion.
pendingToFilter.set(parentPath.join('/'), parent);
// mark element for deletion
setAt(copy, setAtPath, toDelete);
}
else {
const fieldToDelete = setAtPath[setAtPath.length - 1];
delete parent[fieldToDelete];
setAt(copy, parentPath, parent);
}
}
else
setAt(copy, setAtPath, isInclude ? (0, just_clone_1.default)(value) : undefined);
}
}
for (const [parentPath, parent] of pendingToFilter) {
const compactingDeleteItems = parent.filter(el => el !== toDelete);
setAt(copy, parentPath.split('/'), compactingDeleteItems);
}
}
return copy;
}
exports.project = project;
// {
// const users = [
// {
// name: 'Jose',
// age: 47,
// salary: 52000,
// posts: [
// { id: 1, message: 'test', likes: 2 },
// { id: 2, message: 'test1', likes: 2 },
// { id: 3, message: 'test2', likes: 2 },
// ],
// },
// {
// name: 'Luchi',
// age: 49,
// salary: 52000,
// twoLevels: {a:3},
// posts: [
// { id: 1, message: 'testL', likes: 2 },
// { id: 2, message: 'testL1', likes: 2 },
// { id: 3, message: 'testL2', likes: 2 },
// ],
// },
// ]
// const pathToSelect = ['+$', '-$[*].age', '-$[*].twoLevels.a', '-$[*].posts[:-1]'] //, '-$[*].age'];
// console.log(
// JSON.stringify(project(pathToSelect, users),undefined,2),
// JSON.stringify(project(['+$[*].posts[0,2]', '-$[*].posts[1]'], users),undefined, 2),
// JSON.stringify(project(['+$.a.b','-$.a.b.d'], {a:{b:{c:3,d:5,e:9}}}),undefined,2),
// JSON.stringify(project(['+$'], 2),undefined,2)
// )
// }
function copyPropsWithValueUsingRules(objDest, copyRules, shouldUpdateOnlyEmptyFields = false) {
return (inputObj) => {
copyRules.map((rule) => {
let from;
let to;
if (typeof rule === 'object') {
from = rule.from;
to = rule.to;
}
else {
from = rule;
to = rule;
}
let valueToCopy = getAt(inputObj, from);
if (typeof rule.transform === 'function') {
valueToCopy = rule.transform(valueToCopy);
}
if (valueToCopy === undefined || valueToCopy === null)
return;
if (shouldUpdateOnlyEmptyFields === true && isEmpty(getAt(objDest, to)))
setAt(objDest, to, valueToCopy);
if (shouldUpdateOnlyEmptyFields === false)
setAt(objDest, to, valueToCopy);
});
return objDest;
};
}
exports.copyPropsWithValueUsingRules = copyPropsWithValueUsingRules;
// {
// let objTo = {a:{b:2},c:3}
// let objFrom = {a:{b:4},c:8,d:{e:{f:12}},l:5}
// copyPropsWithValueUsingRules(objTo, [{from:'a.b', to:'c'}, {from:'d.e.f', to:'d'}])(objFrom)
// objTo
// }
// {
// let objTo = {a:{b:2},c:3}
// let objFrom = {a:{b:4},c:8,d:{e:{f:12}}, l:5}
// copyPropsWithValueUsingRules(objTo,
// [
// {from:'a.b', to:'c'},
// {from:'d.e.f', to:'d.f'},
// {from:'d.e.g', to:'d.g'}
// ],
// true
// )(objFrom)
// objTo
// }
// {
// let objTo = {a:{b:2},c:12}
// let objFrom = {a:{b:4},g:"2228",d:{e:{f:12}}, l:5}
// copyPropsWithValueUsingRules(objTo,
// [
// {from:'g', to:'e', transform: parseInt},
// {from:'d.e.f', to:'d.f'},
// {from:'d.e.g', to:'d.g'}
// ],
// true
// )(objFrom)
// objTo
// }
function copyPropsWithValue(objDest, shouldUpdateOnlyEmptyFields = false) {
return (inputObj) => {
traverse(inputObj, (nodeValue, currentPath) => {
if (isALeaf(nodeValue) === false)
return;
if (nodeValue === undefined || nodeValue === null || currentPath.length === 1)
return;
const destPath = currentPath.slice(1 - currentPath.length);
if (shouldUpdateOnlyEmptyFields === true) {
const valueAtDest = getAt(objDest, destPath);
if (isEmpty(valueAtDest))
setAt(objDest, destPath, nodeValue);
return;
}
setAt(objDest, destPath, nodeValue); //?
});
return objDest;
};
}
exports.copyPropsWithValue = copyPropsWithValue;
// {
// let objTo = { a: { b: 2 }, c: 3, h: { i: 3 } }
// let objFrom = { a: { b: 4 }, c: undefined, d: { e: { f: 12 } } }
// copyPropsWithValue(objTo)(objFrom) //?
// objTo
// }
// {
// let objTo = { a: { b: 2 }, c: 3, h: { i: 3 } }
// let objFrom = { a: { b: 4 }, c: '', d: { e: { f: 12 } } }
// copyPropsWithValue(objTo, undefined, true)(objFrom) //?
// objTo
// }
function isALeaf(node) {
const isABranch = ((node === null || node === void 0 ? void 0 : node.constructor.name) === 'Object' && Object.keys(node).length > 0) ||
((node === null || node === void 0 ? void 0 : node.constructor.name) === 'Array' && node.length > 0);
return !isABranch;
}
exports.isALeaf = isALeaf;
// isALeaf(undefined) //?
// isALeaf({a:'this is a leaf'}.a) //?
// isALeaf(new Date()) //?
// isALeaf([]) //?
// isALeaf({}) //?
// isALeaf({a:3}) //?
// isALeaf([undefined]) //?
function isEmpty(value) {
if (value === undefined ||
value === null ||
value === '' ||
value === 0 ||
value === 0n ||
Number.isNaN(value) ||
(Array.isArray(value) && (value === null || value === void 0 ? void 0 : value.length) === 0) ||
(typeof value === 'object' && Object.keys(value).length === 0))
return true;
return false;
}
exports.isEmpty = isEmpty;
//isEmpty(0) //?
function firstCapital(str) {
return typeof str === 'string' ? str[0].toUpperCase() + str.substring(1).toLowerCase() : str;
}
exports.firstCapital = firstCapital;
function queryObjToStr(query) {
return Object.keys(query).reduce((acum, current) => {
const newAcum = acum ? `${acum}&` : acum;
return `${newAcum}${current}=${query[current]}`;
}, '');
}
exports.queryObjToStr = queryObjToStr;
function varSubsDoubleBracket(strToResolveVars, state, mode) {
if (typeof strToResolveVars !== 'string') {
return strToResolveVars;
}
// regex to deal with the case the entire value is a substitution group
// let regexVar = /"{{(.*?)(?:=(.*?))?}}"/g
// ask ChatGPT to do a more performant regex without look ahead
const regexVar = /"{{([^=}]+)(?:=([^}]+))?}}"/g;
const resultStr = strToResolveVars.replace(regexVar, (_notused, group1, group2) => {
if (state && state[group1] !== undefined) {
if (typeof state[group1] === 'string')
return `"${state[group1]}"`;
if (typeof state[group1] === 'object') {
if (mode === 'url' && Array.isArray(state[group1]))
return arrayToListQuery(state[group1]);
if (mode === 'url' && !Array.isArray(state[group1]))
return objToQueryParams(state[group1]);
if (!mode)
return JSON.stringify(state[group1]);
}
return state[group1];
}
if (group2 === undefined)
return null;
//else if(group2.substring(0,2) === '\\"' && group2.substring([group2.length -1],2) === '\\"') return ('"' + group2 + '"')
return group2.replace(/\\"/g, '"');
});
// regex to do partial substitution of a group inside of a string value
// let regexVarPartial = /{{(.*?)(?:=(.*?))?}}/g
// ask ChatGPT to do a more performant regex without look ahead
const regexVarPartial = /{{([^=}]+)(?:=([^}]+))?}}/g;
const resultStrFinal = resultStr.replace(regexVarPartial, (_notused, group1, group2) => {
if (state && state[group1] !== undefined) {
if (typeof state[group1] === 'object') {
if (mode === 'url' && Array.isArray(state[group1]))
return arrayToListQuery(state[group1]);
if (mode === 'url' && !Array.isArray(state[group1]))
return objToQueryParams(state[group1]);
if (!mode)
return JSON.stringify(state[group1]);
}
else
return state[group1];
}
else {
if (group2 === undefined)
return null;
return group2.replace(/\\"/g, '');
}
});
return resultStrFinal;
}
exports.varSubsDoubleBracket = varSubsDoubleBracket;
// varSubsDoubleBracket(`
// {
// "response": {
// "id": 1231,
// "description": "{{description=\"This is a test\"}}",
// "slot": "{{slotNumber}}",
// "active": "{{active=true}}",
// "ratenumber": "{{rate=10}}"
// }
// }`, {slotNumber:{a:3}, active:false}) //?
// varSubsDoubleBracket('https://bank.account?accounts={{accounts}}&c=3', {accounts:['10232-1232','2331-1233']},'url') //?
// varSubsDoubleBracket('https://bank.account?{{params=a=1&b=2}}&c=3', {params:{a:'10232-1232',b:'2331-1233'}},'url') //?
// varSubsDoubleBracket('https://bank.account?{{params=a=1&b=2}}&c=3', {},'url') //?
function arrayToListQuery(arr) {
return arr.reduce((prev, curr) => `${prev},${curr}`);
}
function objToQueryParams(obj) {
return Object.keys(obj)
.reduce((acum, curr) => `${acum}${curr}=${obj[curr]}&`, '')
.slice(0, -1);
}
function urlCompose(gatewayUrl, serviceName, servicePath) {
return {
gatewayUrl,
serviceName,
servicePath,
url: `${gatewayUrl}${serviceName}${servicePath}`
};
}
exports.urlCompose = urlCompose;
function urlDecompose(url, listOfServiceNames) {
return listOfServiceNames
.filter(elem => url.split(elem).length >= 2)
.map(elem => {
const [part1, ...restParts] = url.split(elem);
return {
gatewayUrl: part1,
serviceName: elem,
servicePath: restParts.join(elem)
};
});
}
exports.urlDecompose = urlDecompose;
function indexOfNthMatch(stringToInspect, toMatch, nth) {
if (nth > 0 === false)
return -1;
let index = -1;
for (let i = 0; i < nth; i++) {
index = stringToInspect.indexOf(toMatch, index + 1);
}
return index;
}
exports.indexOfNthMatch = indexOfNthMatch;
// indexOfNthMatch('', '£', 0) //?
// indexOfNthMatch('£', '£', 3.12) //?
// indexOfNthMatch('£', '£', 1) //?
// indexOfNthMatch('It will not find', 'loop', 0) //?
// indexOfNthMatch('It will not find', 'loop', 1) //?
// indexOfNthMatch('a.b.c.d.e.f', '£', 3) //?
// indexOfNthMatch('a.b.c.d.e.f', '.', 3) //?
// indexOfNthMatch('a.b.c.d.e.f', '.', 2) //?
// indexOfNthMatch('a.b.c.d.e.f', '.', 1) //?
function toDate(date) {
return date
? date instanceof Date
? date
: new Date(date)
: new Date();
}
function isDate(d) {
return d instanceof Date && !Number.isNaN(d);
}
exports.isDate = isDate;
function isStringADate(stringDate) {
if (typeof stringDate !== 'string')
return false;
return isDate(new Date(stringDate));
}
exports.isStringADate = isStringADate;
function dateFormatter(format) {
return (date) => formatDate(format, date);
}
exports.dateFormatter = dateFormatter;
function formatDate(format, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return undefined;
const months = new EnumMap({
'January': '01',
'February': '02',
'March': '03',
'April': '04',
'May': '05',
'June': '06',
'July': '07',
'August': '08',
'September': '09',
'October': '10',
'November': '11',
'December': '12'
});
const indexMonths = months.invert();
const days = new EnumMap({
'Sunday': '0',
'Monday': '1',
'Tuesday': '2',
'Wednesday': '3',
'Thursday': '4',
'Friday': '5',
'Saturday': '6'
});
const indexDays = days.invert();
const dateIsoString = dateToProcess.toISOString();
const YYYY = dateIsoString.substring(0, 4);
const YY = dateIsoString.substring(2, 4);
const MM = dateIsoString.substring(5, 7);
const DD = dateIsoString.substring(8, 10);
const D = Number.parseInt(DD, 10).toString();
const hh = dateIsoString.substring(11, 13);
const h = Number.parseInt(hh, 10).toString();
const mm = dateIsoString.substring(14, 16);
const ss = dateIsoString.substring(17, 19);
const mil = dateIsoString.substring(20, 23);
const mi = dateIsoString.substring(20, 22);
const month = indexMonths[MM];
const dayOfWeek = indexDays[dateToProcess.getUTCDay()];
return format
.replace(/\$YYYY/g, YYYY)
.replace(/\$YY/g, YY)
.replace(/\$MM/g, MM)
.replace(/\$DD/g, DD)
.replace(/\$D/g, D)
.replace(/\$hh/g, hh)
.replace(/\$h/g, h)
.replace(/\$mm/g, mm)
.replace(/\$ss/g, ss)
.replace(/\$mil/g, mil)
.replace(/\$mi/g, mi)
.replace(/\$month/g, month)
.replace(/\$dayOfWeek/g, dayOfWeek);
}
exports.formatDate = formatDate;
//formatDate('$YYYY-$MM-$DD', new Date('2021-02-28')) //?
function YYYY_MM_DD_hh_mm_ss_ToUtcDate(dateYYYY_MM_DD_hh_mm_ss) {
// Input format has 1 char (any could work) between each elements: years, months, days, hours, minutes and seconds
const dateYYYY = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(0, 4));
const dateMM = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(5, 7)) - 1; // Months start with 0
const dateDD = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(8, 10));
const datehh = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(11, 13));
const datemm = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(14, 16));
const datess = Number.parseInt(dateYYYY_MM_DD_hh_mm_ss.substring(17, 19));
return Date.UTC(dateYYYY, dateMM, dateDD, datehh, datemm, datess);
}
exports.YYYY_MM_DD_hh_mm_ss_ToUtcDate = YYYY_MM_DD_hh_mm_ss_ToUtcDate;
const DAYS = new EnumMap(['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY']);
exports.DAYS = DAYS;
const MONTHS = new EnumMap(['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER']);
exports.MONTHS = MONTHS;
function dateToObj(date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return undefined;
const ISODate = dateToProcess.toISOString();
return {
YYYY: Number.parseInt(ISODate.substring(0, 4)),
MM: Number.parseInt(ISODate.substring(5, 7)),
DD: Number.parseInt(ISODate.substring(8, 10)),
hh: Number.parseInt(ISODate.substring(11, 13)),
mm: Number.parseInt(ISODate.substring(14, 16)),
ss: Number.parseInt(ISODate.substring(17, 19)),
mil: Number.parseInt(ISODate.substring(20, 23))
};
}
exports.dateToObj = dateToObj;
//dateToObj() //?
// const toPeriod = new Date()
// const fromPeriod = `${dateToObj(toPeriod).YYYY - 3}${formatDate('-$MM-$DD', toPeriod)}`
// fromPeriod //?
function diffInDaysYYYY_MM_DD(iniDate, endDate) {
return Math.ceil((new Date(endDate) - new Date(iniDate)) / (1000 * 60 * 60 * 24)); //?
}
exports.diffInDaysYYYY_MM_DD = diffInDaysYYYY_MM_DD;
function addDays(daysToAdd, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
// 864e5 is a valid JavaScript number that represents the number of milliseconds in a 24h
return new Date(dateToProcess.valueOf() + 864E5 * daysToAdd);
}
exports.addDays = addDays;
// addDays(2, "2023-03-24").toISOString() //?
// addDays(3, "2023-03-24").toISOString() //?
// addDays(9, "2023-03-24").toISOString() //?
function subtractDays(daysToSubtract, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
return new Date(dateToProcess.valueOf() - 864E5 * daysToSubtract);
}
exports.subtractDays = subtractDays;
// subtractDays(2, "2023-03-26").toISOString() //?
// subtractDays(3, "2023-03-27").toISOString() //?
// subtractDays(9, "2023-04-02").toISOString() //?
function previousDayOfWeek(dayOfWeek, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
const diffInDaysOfWeek = dateToProcess.getUTCDay() - dayOfWeek;
const toSubtract = diffInDaysOfWeek >= 0
? diffInDaysOfWeek
: 7 + diffInDaysOfWeek;
return subtractDays(toSubtract, dateToProcess);
}
exports.previousDayOfWeek = previousDayOfWeek;
//previousDayOfWeek(6,new Date('2021-05-07')) //?
//previousDayOfWeek(1,new Date('2021-03-25')) //?
function nextDayOfWeek(dayOfWeek, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
const diffInDaysOfWeek = dayOfWeek - dateToProcess.getUTCDay();
diffInDaysOfWeek;
const toAdd = diffInDaysOfWeek >= 0
? diffInDaysOfWeek
: 7 + diffInDaysOfWeek;
return addDays(toAdd, dateToProcess);
}
exports.nextDayOfWeek = nextDayOfWeek;
// nextDayOfWeek(0,new Date('2025-02-01')) //?
// nextDayOfWeek(1,new Date('2021-03-25')) //?
function dayOfWeek(dayOfWeek, date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
const diffInDaysOfWeek = (dayOfWeek === 0 ? 7 : dayOfWeek) -
(dateToProcess.getUTCDay() === 0 ? 7 : dateToProcess.getUTCDay());
diffInDaysOfWeek;
const toSubtract = diffInDaysOfWeek;
// diffInDaysOfWeek >= 0
// ? diffInDaysOfWeek
// : 7 + diffInDaysOfWeek
return addDays(toSubtract, dateToProcess);
}
exports.dayOfWeek = dayOfWeek;
// dayOfWeek(0,new Date('2025-02-06')) //?
// dayOfWeek(6,new Date('2025-02-09')) //?
function getSameDateOrPreviousFridayForWeekends(date) {
const dateToProcess = toDate(date);
if (isDate(dateToProcess) === false)
return dateToProcess;
const dayOfWeek = dateToProcess.getUTCDay();
if (dayOfWeek > 0 && dayOfWeek < 6)
return dateToProcess;
//Sunday
if (dayOfWeek === 0)
return subtractDays(2, dateToProcess);
//Saturday (dayOfWeek === 6)
return subtractDays(1, dateToProcess);
}
exports.getSameDateOrPreviousFridayForWeekends = getSameDateOrPreviousFridayForWeekends;
// getSameDateOrPreviousFridayForWeekends() //?
// //2021-05-14T00:00:00.000Z
// getSameDateOrPreviousFridayForWeekends(new Date('2021-05-15')).toISOString() //?
// ////2021-05-14T00:00:00.000Z
// getSameDateOrPreviousFridayForWeekends(new Date('2021-05-16')).toISOString() //?
function isDateMidnight(date) {
var _a, _b;
return ((_b = (_a = date === null || date === void 0 ? void 0 : date.toISOString) === null || _a === void 0 ? void 0 : _a.call(date)) === null || _b === void 0 ? void 0 : _b.substring(10, 24)) === 'T00:00:00.000Z';
}
exports.isDateMidnight = isDateMidnight;
function setDateToMidnight(date) {
if (typeof date === 'string' && date.match(/\d{4}\D\d{2}\D\d{2}/))
return new Date(`${date.substring(0, 10)} UTC`);
if (typeof date === 'string')
return new Date(`${date} UTC`);
const dateToProcess = (
// biome-ignore lint/style/noArguments: <explanation>
arguments.length === 0
? new Date()
: new Date(date));
if (Number.isNaN(+dateToProcess))
return dateToProcess;
if (isDateMidnight(dateToProcess))
return dateToProcess;
return new Date(`${dateToProcess.toISOString().substring(0, 10)} UTC`);
}
exports.setDateToMidnight = setDateToMidnight;
const { colors, colorMessage, colorMessageByStatus, colorByStatus } = (() => {
//alias
const colors = {
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
blue: '\x1b[34m',
//most importants
reset: '\x1b[0m',
reverse: '\x1b[7m',
fgBlack: '\x1b[30m',
fgRed: '\x1b[31m',
fgGreen: '\x1b[32m',
fgYellow: '\x1b[33m',
//Others
fgBlue: '\x1b[34m',
fgMagenta: '\x1b[35m',
fgCyan: '\x1b[36m',
fgWhite: '\x1b[37m',
bgBlack: '\x1b[40m',
bgRed: '\x1b[41m',
bgGreen: '\x1b[42m',
bgYellow: '\x1b[43m',
bgBlue: '\x1b[44m',
bgMagenta: '\x1b[45m',
bgCyan: '\x1b[46m',
bgWhite: '\x1b[47m',
bright: '\x1b[1m',
dim: '\x1b[2m',
underscore: '\x1b[4m',
blink: '\x1b[5m',
hidden: '\x1b[8m'
};
const colorMessage = (message, color) => `${colors[color]}${message}${colors.reset}`;
const colorMessageByStatus = (_message, status) => {
return (status >= 200) & (status < 300)
? colors.green
: (status >= 300) & (status < 400)
? colors.cyan
: (status >= 400) & (status < 500)
? colors.yellow
: colors.red;
};
const colorByStatus = status => {
return (status >= 200) & (status < 300)
? colors.green
: (status >= 300) & (status < 400)
? colors.cyan
: (status >= 400) & (status < 500)
? colors.yellow
: colors.red;
};
return { colors, colorMessage, colorMessageByStatus, colorByStatus };
})();
exports.colors = colors;
exports.colorMessage = colorMessage;
exports.colorMessageByStatus = colorMessageByStatus;
exports.colorByStatus = colorByStatus;
function findDeepKey(objIni, keyToFind) {
const currentPath = [];
const result = [];
traverse(objIni);
function traverse(obj) {
for (const prop in obj) {
if (prop === keyToFind) {
result.push([...currentPath]);
result[result.length - 1].push(prop);
result.push(obj[prop]);
}
if (obj[prop] !== null && typeof obj[prop] === 'object') {
currentPath.push(prop);
traverse(obj[prop]);
currentPath.pop();
}