Socialify

Folder ..

Viewing unique.ts
121 lines (106 loc) • 3.5 KB

  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
// global results store
// currently uniqueness is global to entire faker instance
// this means that faker should currently *never* return duplicate values across all API methods when using `Faker.unique`
// it's possible in the future that some users may want to scope found per function call instead of faker instance
const found: Record<string, string> = {};

// global exclude list of results
// defaults to nothing excluded
const exclude: string[] = [];

// current iteration or retries of unique.exec ( current loop depth )
const currentIterations = 0;

// uniqueness compare function
// default behavior is to check value as key against object hash
function defaultCompare<T, Key extends keyof T>(obj: T, key: Key): 0 | -1 {
  if (typeof obj[key] === 'undefined') {
    return -1;
  }
  return 0;
}

// common error handler for messages
function errorMessage(
  now: number,
  code: string,
  opts: { startTime: number }
): never {
  console.error('error', code);
  console.log(
    'found',
    Object.keys(found).length,
    'unique entries before throwing error. \nretried:',
    currentIterations,
    '\ntotal time:',
    now - opts.startTime,
    'ms'
  );
  throw new Error(
    code +
      ' for uniqueness check \n\nMay not be able to generate any more unique values with current settings. \nTry adjusting maxTime or maxRetries parameters for faker.unique()'
  );
}

// TODO @Shinigami92 2022-01-24: We should investigate deeper into the types
// Especially the `opts.compare` parameter and `Result` type
export function exec<Method extends (args: Args) => string, Args extends any[]>(
  method: Method,
  args: Args,
  opts: {
    maxTime?: number;
    maxRetries?: number;
    exclude?: string | string[];
    compare?: (obj: Record<string, string>, key: string) => 0 | -1;
    currentIterations?: number;
    startTime?: number;
  }
): string {
  const now = new Date().getTime();

  opts = opts || {};
  opts.maxTime = opts.maxTime || 3;
  opts.maxRetries = opts.maxRetries || 50;
  opts.exclude = opts.exclude || exclude;
  opts.compare = opts.compare || defaultCompare;

  if (typeof opts.currentIterations !== 'number') {
    opts.currentIterations = 0;
  }

  if (typeof opts.startTime === 'undefined') {
    opts.startTime = new Date().getTime();
  }

  const startTime = opts.startTime;

  // support single exclude argument as string
  if (typeof opts.exclude === 'string') {
    opts.exclude = [opts.exclude];
  }

  if (opts.currentIterations > 0) {
    // console.log('iterating', currentIterations)
  }

  // console.log(now - startTime)
  if (now - startTime >= opts.maxTime) {
    return errorMessage(
      now,
      'Exceeded maxTime:' + opts.maxTime,
      // @ts-expect-error: we know that opts.startTime is defined
      opts
    );
  }

  if (opts.currentIterations >= opts.maxRetries) {
    return errorMessage(
      now,
      'Exceeded maxRetries:' + opts.maxRetries,
      // @ts-expect-error: we know that opts.startTime is defined
      opts
    );
  }

  // execute the provided method to find a potential satisfied value
  const result: string = method.apply(this, args);

  // if the result has not been previously found, add it to the found array and return the value as it's unique
  if (
    opts.compare(found, result) === -1 &&
    opts.exclude.indexOf(result) === -1
  ) {
    found[result] = result;
    opts.currentIterations = 0;
    return result;
  } else {
    // console.log('conflict', result);
    opts.currentIterations++;
    return exec(method, args, opts);
  }
}