Skip to content
Snippets Groups Projects
util.ts 7.32 KiB
Newer Older
import { CommandType, DecodeOptions } from "../types";
// import set from "lodash.set";
Marin Karamihalev's avatar
Marin Karamihalev committed
const digitDotRe = /^\d+\..*$/;
const digitRe = /^\d+$/;
export const isDigit = (v: any) => digitRe.test(v);
Marin Karamihalev's avatar
Marin Karamihalev committed
const firstIsIndex = (s: string) => digitDotRe.test(s);

// based on https://stackoverflow.com/questions/54733539/javascript-implementation-of-lodash-set-method
const set = (obj, path, value) => {
  if (Object(obj) !== obj) return obj;
  if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || [];
  path
    .slice(0, -1)
    .reduce(
      (a, c, i) =>
        Object(a[c]) === a[c]
          ? a[c]
          : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}),
      obj
    )[path[path.length - 1]] = value;
  return obj;
};

Marin Karamihalev's avatar
Marin Karamihalev committed
// Based on: https://www.30secondsofcode.org/js/s/unflatten-object
Marin Karamihalev's avatar
Marin Karamihalev committed
export const unflatten = (obj: any) =>
  Object.keys(obj).reduce(
    (res, k) => {
      k.split(".")
        .map((v) => (isDigit(v) ? parseInt(v) - 1 : v))
        .reduce(
          (acc: any, e, i, keys) =>
            acc[e] ||
            (acc[e] = isNaN(Number(keys[i + 1]))
              ? keys.length - 1 === i
                ? obj[k]
                : {}
              : []),
          res
        );
      return res;
    },
    firstIsIndex(Object.keys(obj)[0]) ? [] : {}
  );

export const search = (obj: any, key: string): any => {
  if (typeof obj !== "object") return null;
  if (obj[key]) return obj[key];
  for (const val of Object.values(obj)) {
    const s = search(val, key);
    if (s) return s;
  }
};

export const searchParent = (
  obj: any,
  key: string
): Record<string, any> | undefined => {
  if (typeof obj !== "object") return;
  if (obj[key]) return obj;
  for (const val of Object.values(obj)) {
    const s = searchParent(val, key);
    if (s) return s;
  }
};
const fixPath = (s: string): string =>
  s
    .split(".")
    .filter((it) => it !== "")
    .map((it) => (isDigit(it) ? `[${parseInt(it) - 1}]` : it))
    .join(".")
    .split(".[")
    .join("[");

export const convertToNestedObject = (arr: any[], retainPath?: boolean) => {
    .map((it) =>
      Object.entries(it.resultParams).map(([key, value]) => ({
        path: fixPath(it.resolvedPath + key),
        value,
      }))
    )
    .flat(1)
    .sort((a, b) => a.path.localeCompare(b.path))
    .forEach(({ path, value }) => {
      set(res, path, value);
    });
  return res;
};

export const hasMultipleIndexes = (
  arr: string[],
  pathSplit: string[]
): boolean =>
  arr.some((it) => {
    const spl = it.split(".");
    return spl.length > pathSplit.length && isDigit(spl[pathSplit.length - 1]);
  });
Marin Karamihalev's avatar
Marin Karamihalev committed
const _searchAll = (obj: any, key: string): any[] =>
  typeof obj !== "object"
    ? []
    : Object.entries(obj).reduce(
        (acc, [k, v]) => [...acc, k === key ? v : _searchAll(v, key)],
        [] as any[]
      );

export const searchAll = (obj: any, key: string) =>
  _searchAll(obj, key).flat(Infinity);

export const extractCommand = (msg: {
  [key: string]: any;
}): CommandType | undefined => {
  const msgType: string | undefined = search(msg, "msgType");
  if (!msgType) {
    const id: string | undefined = search(msg, "msgId");
    const [frst] = id ? id.split("@") : [""];
    return frst.toUpperCase().replace("_RESP", "") as CommandType;
  return msgType.replace("_RESP", "") as CommandType;
Marin Karamihalev's avatar
Marin Karamihalev committed
/** Unwraps object with single key */
Marin Karamihalev's avatar
Marin Karamihalev committed
export const unwrapObject = (data: any): any =>
  !Array.isArray(data) &&
  typeof data === "object" &&
  Object.keys(data).length === 1
    ? Object.values(data)[0]
    : data;

Marin Karamihalev's avatar
Marin Karamihalev committed
/** Unwraps array with single item */
Marin Karamihalev's avatar
Marin Karamihalev committed
export const unwrapArray = (arr: any) =>
  Array.isArray(arr) && arr.length === 1 ? arr[0] : arr;
const isObject = (val: any): boolean =>
  typeof val === "object" && val !== null && !Array.isArray(val);

export const isEmptyObject = (obj: any): boolean =>
  obj !== null &&
  obj !== undefined &&
  obj &&
  typeof obj === "object" &&
  Object.keys(obj).length === 0;

export const isEmpty = (v: any): boolean =>
  v === undefined || v === null || isEmptyObject(v);

const clearObj = (obj) =>
  isObject(obj) && Object.keys(obj).length === 1 ? Object.values(obj)[0] : obj;

const clearArray = (arr: Array<any>) =>
  arr.length === 1 && Array.isArray(arr[0]) ? arr[0] : arr;

export const fullyUnwrapObject = (obj: any, shouldBeArray: boolean) => {
  if (isObject(obj) && Object.keys(obj).length === 1)
    return fullyUnwrapObject(Object.values(obj)[0], shouldBeArray);
  const isArray = Array.isArray(obj);
  if (shouldBeArray)
    if (isArray)
      return clearArray(obj.filter((v) => !isEmptyObject(v)).map(clearObj));
    else return isEmptyObject(obj) ? [] : [clearObj(obj)];
  else if (isArray)
    return fullyUnwrapObject(
      clearObj(obj.find((v) => !isEmpty(v))),
      shouldBeArray
    );
export function makeBuffer(
  rootRecord: any,
  payload: any,
  options: Record<string, string>
) {
  const NoSessionContextRecord = rootRecord.lookupType(
    "usp_record.NoSessionContextRecord"
  );
  const noSessionContextRecordMsg = NoSessionContextRecord.create({
    payload,
  });
  const record: any = rootRecord.lookupType("usp_record.Record");
  const recordMsg = record.create({
    version: "1.0",
    PayloadSecurity: record.PayloadSecurity.PLAINTEXT,
    noSessionContext: noSessionContextRecordMsg,
    ...options,
  });
  const buffer = record.encode(recordMsg).finish();
  return buffer;
}

export const uniq = (initial?: string): string =>
  (initial || "") +
  (
    Date.now().toString(36) + Math.random().toString(36).substr(2, 5)

export const parseID = (msg: any) => {
  const foundId = search(msg, "msgId");
  // if id is formatted by me (command@) then use, otherwise check for sub id
  const id = foundId.includes("@")
    ? foundId
    : search(msg, "subscriptionId") || null;
  return id;
};

const addPaths = (arr: any[]) =>
  arr.map(({ resolvedPath, resultParams }) => ({
    resolvedPath,
    resultParams: { ...resultParams, query: resolvedPath },
  }));

type RetainPathGetResult = {
  query: string;
  result: string | Record<string, string> | RetainPathGetResult[];
};

const getOther = (obj: Record<string, string>, skipKey: string) =>
  obj[Object.keys(obj).find((key) => key !== skipKey) || ""];

const skipKey = (obj: Record<string, string>, keyToSkip: string) =>
  Object.entries(obj).reduce(
    (acc, [key, val]) => (key !== keyToSkip ? { ...acc, [key]: val } : acc),
    {}
  );

const isValue = (obj: Record<string, string>) => Object.keys(obj).length === 2;

const nestResult = (
  result: any,
  query?: string
): RetainPathGetResult => ({
  query: query || result.query,
  result: Array.isArray(result)
    ? result.map((v) => nestResult(v))
    : isValue(result)
    ? getOther(result, "query")
    : skipKey(result, "query"),
});

export const processGetResult = (
  resolvedPathResultsArr: any[],
  shouldBeArray: boolean,
  requestedPath: string,
  decodeOptions?: DecodeOptions,
) => {
  const retainPath = decodeOptions?.retainPath === true;
  const resolvedValues = retainPath
    ? addPaths(resolvedPathResultsArr)
    : resolvedPathResultsArr;
  const unflattened = convertToNestedObject(resolvedValues);
  const unwrapped = fullyUnwrapObject(unflattened, shouldBeArray);
  const result = isEmptyObject(unwrapped) ? [] : unwrapped;
  const modifiedResult = retainPath
    ? nestResult(result, requestedPath)
    : result;
  return modifiedResult;
};