@snickerdoodlelabs/common-utils
Version:
Common utils classes used in snickerdoodlelabs projects
198 lines • 7.76 kB
JavaScript
import { PagingRequest, JSONString, InvalidParametersError, } from "@snickerdoodlelabs/objects";
import { errAsync, okAsync } from "neverthrow";
import { ResultUtils } from "neverthrow-result-utils";
export class ObjectUtils {
// Taken from https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static mergeDeep(...objects) {
const isObject = (obj) => obj && typeof obj === "object";
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach((key) => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = ObjectUtils.mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
static serialize(obj) {
return JSONString(JSON.stringify(obj, (key, value) => {
if (value instanceof Map) {
return {
dataType: "Map",
value: Array.from(value.entries()), // or with spread: value: [...value]
};
}
else if (value instanceof Set) {
return {
dataType: "Set",
value: [...value],
};
}
else if (value instanceof BigInt) {
return {
dataType: "BigInt",
value: value.toString(),
};
}
else if (typeof value == "bigint") {
return {
dataType: "bigint",
value: BigInt(value).toString(),
};
}
else {
return value;
}
}));
}
static deserialize(json) {
return JSON.parse(json, (key, value) => {
if (typeof value === "object" && value !== null) {
if (value.dataType === "Map") {
return new Map(value.value);
}
else if (value.dataType === "Set") {
return new Set(value.value);
}
else if (value.dataType === "BigInt") {
return BigInt(value.value);
}
else if (value.dataType === "bigint") {
return BigInt(value.value);
}
}
return value;
});
}
static toGenericObject(obj) {
return ObjectUtils.deserialize(ObjectUtils.serialize(obj));
}
static iteratePages(readFunc, processFunc, pageSize = 25, serialize = false) {
// This is a recursive method. We just start it off.
return ObjectUtils.fetchAndProcessPage(1, readFunc, processFunc, pageSize, serialize);
}
static fetchAndProcessPage(pageNumber, readFunc, processFunc, pageSize = 25, serialize = false) {
// Create the initial paging request
const pagingRequest = new PagingRequest(pageNumber, pageSize);
// Read the page
return readFunc(pagingRequest).andThen((objectPage) => {
// Iterate over the objects and run through the processor
// We'll wait for all the processFuncs to complete before getting the next page
let result;
if (serialize) {
result = ResultUtils.executeSerially(objectPage.response.map((obj) => () => {
return processFunc(obj);
}));
}
else {
result = ResultUtils.combine(objectPage.response.map((obj) => {
return processFunc(obj);
}));
}
return result.andThen(() => {
// Done processing all of those results. Check if we need to recurse
// See what result we're on and how it compares to the total results
const maxResult = objectPage.page * objectPage.pageSize;
if (maxResult < objectPage.totalResults) {
return ObjectUtils.fetchAndProcessPage(objectPage.page + 1, readFunc, processFunc, pageSize);
}
return okAsync(undefined);
});
});
}
static iterateCursor(readFunc, handler) {
// Do the first read
return ObjectUtils.processNextBatch(null, readFunc, handler).map(() => { });
}
static parseBoolean(val) {
switch (typeof val) {
case "boolean":
return val;
case "number":
return val !== 0;
case "string":
switch (val?.toLowerCase()?.trim()) {
case "true":
case "yes":
case "1":
return true;
case "false":
case "no":
case "0":
case null:
case undefined:
return false;
default:
console.log(`Don't have explicit check in parseBoolean for the string ${val}`);
return false;
}
default:
console.log(`Don't have check in parseBoolean for typeof ${typeof val} of value ${val}`);
return false;
}
}
static processNextBatch(cursor, readFunc, handler) {
return readFunc(cursor).andThen((resp) => {
if (resp.response.length == 0) {
return okAsync(resp);
}
return handler(resp.response).andThen(() => {
return ObjectUtils.processNextBatch(resp.cursor, readFunc, handler);
});
});
}
static removeNullValues(array) {
function notEmpty(value) {
if (value === null || value === undefined)
return false;
const testConversion = value;
return true;
}
return array.filter(notEmpty);
}
static progressiveFallback(method, provider) {
if (provider.length == 0) {
return errAsync(new InvalidParametersError("No providers provided to progressiveFallback()"));
}
return method(provider[0]).orElse((err) => {
// If we're on the last provider, just return the error
if (provider.length == 1) {
return errAsync(err);
}
// Otherwise, try the next provider
return ObjectUtils.progressiveFallback(method, provider.slice(1));
});
}
static verifyBigNumber(bigNumberString) {
try {
const bigNumber = BigInt(bigNumberString); // will fail if bigNumberString is a float
return okAsync(bigNumber);
}
catch (e) {
return errAsync(new InvalidParametersError(`Can't convert BigNumberString ${bigNumberString} to BigInt. Received error ${e}`));
}
}
static iterateThroughAllPages(readFunc) {
const data = [];
const processFunc = (model) => {
data.push(model);
return okAsync(undefined);
};
return readFunc(new PagingRequest(1, 1))
.andThen((firstPage) => {
const pageSize = firstPage.totalResults;
return ObjectUtils.iteratePages(readFunc, processFunc, pageSize);
})
.map(() => data);
}
}
//# sourceMappingURL=ObjectUtils.js.map