Skip to content

Commit

Permalink
feat: resettable unique store (#800)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shinigami92 committed Apr 22, 2022
1 parent a9048f8 commit 29bba7b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 17 deletions.
2 changes: 2 additions & 0 deletions src/unique.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export class Unique {
* @param options.currentIterations This parameter does nothing.
* @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`.
* @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key.
* @param options.store The store of unique entries. Defaults to a global store.
*
* @example
* faker.unique(faker.name.firstName) // 'Corbin'
Expand All @@ -123,6 +124,7 @@ export class Unique {
currentIterations?: number;
exclude?: RecordKey | RecordKey[];
compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1;
store?: Record<RecordKey, RecordKey>;
} = {}
): ReturnType<Method> {
const { maxTime = this._maxTime, maxRetries = this._maxRetries } = options;
Expand Down
47 changes: 30 additions & 17 deletions src/utils/unique.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type RecordKey = string | number | symbol;

/**
* Global store of unique values.
* This means that faker should *never* return duplicate values across all API methods when using `Faker.unique`.
* This means that faker should *never* return duplicate values across all API methods when using `Faker.unique` without passing `options.store`.
*/
const GLOBAL_UNIQUE_STORE: Record<RecordKey, RecordKey> = {};

Expand All @@ -14,11 +14,6 @@ const GLOBAL_UNIQUE_STORE: Record<RecordKey, RecordKey> = {};
*/
const GLOBAL_UNIQUE_EXCLUDE: RecordKey[] = [];

/**
* 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.
Expand All @@ -43,15 +38,21 @@ function defaultCompare(
* @param startTime The time the execution started.
* @param now The current time.
* @param code The error code.
* @param store The store of unique entries.
* @param currentIterations Current iteration or retries of `unique.exec` (current loop depth).
*
* @throws The given error code with additional text.
*/
function errorMessage(startTime: number, now: number, code: string): never {
function errorMessage(
startTime: number,
now: number,
code: string,
store: Record<RecordKey, RecordKey>,
currentIterations: number
): never {
console.error('Error', code);
console.log(
`Found ${
Object.keys(GLOBAL_UNIQUE_STORE).length
} unique entries before throwing error.
`Found ${Object.keys(store).length} unique entries before throwing error.
retried: ${currentIterations}
total time: ${now - startTime}ms`
);
Expand All @@ -77,6 +78,7 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`
* @param options.currentIterations The current attempt. Defaults to `0`.
* @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`.
* @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key.
* @param options.store The store of unique entries. Defaults to `GLOBAL_UNIQUE_STORE`.
*/
export function exec<Method extends (...parameters) => RecordKey>(
method: Method,
Expand All @@ -88,6 +90,7 @@ export function exec<Method extends (...parameters) => RecordKey>(
currentIterations?: number;
exclude?: RecordKey | RecordKey[];
compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1;
store?: Record<RecordKey, RecordKey>;
} = {}
): ReturnType<Method> {
const now = new Date().getTime();
Expand All @@ -97,6 +100,7 @@ export function exec<Method extends (...parameters) => RecordKey>(
maxTime = 50,
maxRetries = 50,
compare = defaultCompare,
store = GLOBAL_UNIQUE_STORE,
} = options;
let { exclude = GLOBAL_UNIQUE_EXCLUDE } = options;
options.currentIterations = options.currentIterations ?? 0;
Expand All @@ -112,22 +116,31 @@ export function exec<Method extends (...parameters) => RecordKey>(

// console.log(now - startTime)
if (now - startTime >= maxTime) {
return errorMessage(startTime, now, `Exceeded maxTime: ${maxTime}`);
return errorMessage(
startTime,
now,
`Exceeded maxTime: ${maxTime}`,
store,
options.currentIterations
);
}

if (options.currentIterations >= maxRetries) {
return errorMessage(startTime, now, `Exceeded maxRetries: ${maxRetries}`);
return errorMessage(
startTime,
now,
`Exceeded maxRetries: ${maxRetries}`,
store,
options.currentIterations
);
}

// Execute the provided method to find a potential satisfied value.
const result: ReturnType<Method> = 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 (
compare(GLOBAL_UNIQUE_STORE, result) === -1 &&
exclude.indexOf(result) === -1
) {
GLOBAL_UNIQUE_STORE[result] = result;
if (compare(store, result) === -1 && exclude.indexOf(result) === -1) {
store[result] = result;
options.currentIterations = 0;
return result;
} else {
Expand Down
16 changes: 16 additions & 0 deletions test/unique.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,20 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`)
expect(options.exclude).toBe(exclude);
expect(options.compare).toBe(compare);
});

it('should be possible to pass a user-specific store', () => {
const store = {};

const method = () => 'with conflict: 0';

expect(faker.unique(method, [], { store })).toBe('with conflict: 0');
expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' });

expect(() => faker.unique(method, [], { store })).toThrow();

delete store['with conflict: 0'];

expect(faker.unique(method, [], { store })).toBe('with conflict: 0');
expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' });
});
});

0 comments on commit 29bba7b

Please sign in to comment.