All files / src query.ts

0% Statements 0/83
0% Branches 0/41
0% Functions 0/16
0% Lines 0/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136                                                                                                                                                                                                                                                                               
import { QueryOperation } from "./instance";
import {QueryMethod, Attribute, Facet, QueryConfiguration} from "./instance";
 
const FILTER_OPERATIONS = ["eq","gt","lt","gte","lte","between","begins","exists","notExists","contains","notContains"] as const;
 
export type BuildQueryParameters = {name: string, query: QueryMethod, attributes: Attribute[], facets: Facet[], actions: {remove?: QueryMethod}};
 
export type ExecuteQueryOptions = {
  raw?: boolean;
  params?: boolean;
  table?: string;
  limit: string;
  filter: FilterOption[]
  delete?: boolean;
}
 
type FilterOperation = (typeof FILTER_OPERATIONS)[number];
 
export type FilterOption = {
  attribute: string;
  operation: FilterOperation
  value1: string;
  value2?: string;
}
 
export type RequestFilters = string | string[];
 
export function getFilterParser(attributes: string[]) {
  return (val: string, arr: FilterOption[] = []): FilterOption[] => {
    let [name = "", operation, value1 = "", value2] = val.split(" ");
    let attribute = attributes.find(attribute => attribute.toLowerCase() === name.toLowerCase());
    if (name === undefined || operation === undefined || value1 === undefined) {
      throw new Error(`Where expressions must be in the format of "<attribute> <operation> <value1> [value2]"`);
    }
    if (!attribute) {
      throw new Error(`Where attribute ${name} is not a valid attribute. Valid attributes include ${attributes.join(", ")}.`);
    }
    if (!isOperation(operation)) {
      throw new Error(`Where operation ${operation} is not a valid attribute. Valid attributes include ${FILTER_OPERATIONS.join(", ")}.`);
    }
    arr.push({attribute, operation, value1, value2});
    return arr;
  }
}
 
export function parseFilters(attributes: string[], filters: RequestFilters) {
  let parser = getFilterParser(attributes);
  if (typeof filters === "string") {
    return parser(filters);
  } else {
    return filters.reduce((result: FilterOption[], value: string) => {
      return parser(value, result)
    }, []);
  }
}
 
export function applyFilter(query: QueryOperation, filters: FilterOption[]): QueryOperation {
  for (let filter of filters) {
    query.where((attr, op) => {
      if (filter.value2) {
        return `${op[filter.operation](attr[filter.attribute], filter.value1, filter.value2)}`
      } else if (filter.value1) {
        return `${op[filter.operation](attr[filter.attribute], filter.value1)}`
      } else {
        return `${op[filter.operation](attr[filter.attribute])}`
      }
    })
  }
  return query;
}
 
export function isOperation(operation: string): operation is FilterOperation {
  return !!FILTER_OPERATIONS.find(op => op  === operation)
}
 
async function removeRecords(data: object[], remove: QueryMethod, options: ExecuteQueryOptions): Promise<object> {
  let results: [string, object][] = await Promise.all(data.map((result: object) => {
    return execute(remove(result), Object.assign({}, options))
      .then((): [string, object] => {
        return ["", result];
      })
      .catch((err: Error): [string, object] => {
        return [err.message, result];
      })
  }));
  // let errors = Array.from(new Set(results.map(([err]) => err))).filter(Boolean);
  // let success = [];
  let failure = [];
  for (let [err, result] of results) {
    if (err) {
      failure.push(result);
    }
  }
  return failure;
}
 
export function parseFacets(args: string[], facets: Facet[]): object {
  let result: {[key: string]: string} = {};
  for (let i = 0; i < facets.length; i++) {
    let name = facets[i].name;
    let value: undefined | string = args[i];
    if (value) {
      result[name] = value;
    }
  }
  return result;
}
 
export async function execute(query: QueryOperation, options: ExecuteQueryOptions): Promise<any> {
  query = applyFilter(query, options.filter);
 
  let config: QueryConfiguration = {};
  if (options.table) {
    config.params = config.params || {};
    config.params.TableName = options.table;
  }
  if (options.limit) {
    config.params = config.params || {};
    config.params.Limit = parseInt(options.limit)
  }
 
  if (options.params) {
    return console.log(query.params(config));
  }
 
  return query.go(config);
}
 
export async function query(params: BuildQueryParameters, options: ExecuteQueryOptions, ...args: string[]) {
  let facets = parseFacets(args, params.facets);
  let data: any = await execute(params.query(facets), options);
  if (options.delete && params.actions.remove !== undefined) {
    data = await removeRecords(data, params.actions.remove, options);
  }
  return data;
}