Skip to content

Commit 29bba7b

Browse files
authored
feat: resettable unique store (#800)
1 parent a9048f8 commit 29bba7b

3 files changed

Lines changed: 48 additions & 17 deletions

File tree

src/unique.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class Unique {
109109
* @param options.currentIterations This parameter does nothing.
110110
* @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`.
111111
* @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key.
112+
* @param options.store The store of unique entries. Defaults to a global store.
112113
*
113114
* @example
114115
* faker.unique(faker.name.firstName) // 'Corbin'
@@ -123,6 +124,7 @@ export class Unique {
123124
currentIterations?: number;
124125
exclude?: RecordKey | RecordKey[];
125126
compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1;
127+
store?: Record<RecordKey, RecordKey>;
126128
} = {}
127129
): ReturnType<Method> {
128130
const { maxTime = this._maxTime, maxRetries = this._maxRetries } = options;

src/utils/unique.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type RecordKey = string | number | symbol;
44

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

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

17-
/**
18-
* Current iteration or retries of `unique.exec` (current loop depth).
19-
*/
20-
const currentIterations = 0;
21-
2217
/**
2318
* Uniqueness compare function.
2419
* Default behavior is to check value as key against object hash.
@@ -43,15 +38,21 @@ function defaultCompare(
4338
* @param startTime The time the execution started.
4439
* @param now The current time.
4540
* @param code The error code.
41+
* @param store The store of unique entries.
42+
* @param currentIterations Current iteration or retries of `unique.exec` (current loop depth).
4643
*
4744
* @throws The given error code with additional text.
4845
*/
49-
function errorMessage(startTime: number, now: number, code: string): never {
46+
function errorMessage(
47+
startTime: number,
48+
now: number,
49+
code: string,
50+
store: Record<RecordKey, RecordKey>,
51+
currentIterations: number
52+
): never {
5053
console.error('Error', code);
5154
console.log(
52-
`Found ${
53-
Object.keys(GLOBAL_UNIQUE_STORE).length
54-
} unique entries before throwing error.
55+
`Found ${Object.keys(store).length} unique entries before throwing error.
5556
retried: ${currentIterations}
5657
total time: ${now - startTime}ms`
5758
);
@@ -77,6 +78,7 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`
7778
* @param options.currentIterations The current attempt. Defaults to `0`.
7879
* @param options.exclude The value or values that should be excluded/skipped. Defaults to `[]`.
7980
* @param options.compare The function used to determine whether a value was already returned. Defaults to check the existence of the key.
81+
* @param options.store The store of unique entries. Defaults to `GLOBAL_UNIQUE_STORE`.
8082
*/
8183
export function exec<Method extends (...parameters) => RecordKey>(
8284
method: Method,
@@ -88,6 +90,7 @@ export function exec<Method extends (...parameters) => RecordKey>(
8890
currentIterations?: number;
8991
exclude?: RecordKey | RecordKey[];
9092
compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1;
93+
store?: Record<RecordKey, RecordKey>;
9194
} = {}
9295
): ReturnType<Method> {
9396
const now = new Date().getTime();
@@ -97,6 +100,7 @@ export function exec<Method extends (...parameters) => RecordKey>(
97100
maxTime = 50,
98101
maxRetries = 50,
99102
compare = defaultCompare,
103+
store = GLOBAL_UNIQUE_STORE,
100104
} = options;
101105
let { exclude = GLOBAL_UNIQUE_EXCLUDE } = options;
102106
options.currentIterations = options.currentIterations ?? 0;
@@ -112,22 +116,31 @@ export function exec<Method extends (...parameters) => RecordKey>(
112116

113117
// console.log(now - startTime)
114118
if (now - startTime >= maxTime) {
115-
return errorMessage(startTime, now, `Exceeded maxTime: ${maxTime}`);
119+
return errorMessage(
120+
startTime,
121+
now,
122+
`Exceeded maxTime: ${maxTime}`,
123+
store,
124+
options.currentIterations
125+
);
116126
}
117127

118128
if (options.currentIterations >= maxRetries) {
119-
return errorMessage(startTime, now, `Exceeded maxRetries: ${maxRetries}`);
129+
return errorMessage(
130+
startTime,
131+
now,
132+
`Exceeded maxRetries: ${maxRetries}`,
133+
store,
134+
options.currentIterations
135+
);
120136
}
121137

122138
// Execute the provided method to find a potential satisfied value.
123139
const result: ReturnType<Method> = method.apply(this, args);
124140

125141
// If the result has not been previously found, add it to the found array and return the value as it's unique.
126-
if (
127-
compare(GLOBAL_UNIQUE_STORE, result) === -1 &&
128-
exclude.indexOf(result) === -1
129-
) {
130-
GLOBAL_UNIQUE_STORE[result] = result;
142+
if (compare(store, result) === -1 && exclude.indexOf(result) === -1) {
143+
store[result] = result;
131144
options.currentIterations = 0;
132145
return result;
133146
} else {

test/unique.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,20 @@ Try adjusting maxTime or maxRetries parameters for faker.unique().`)
210210
expect(options.exclude).toBe(exclude);
211211
expect(options.compare).toBe(compare);
212212
});
213+
214+
it('should be possible to pass a user-specific store', () => {
215+
const store = {};
216+
217+
const method = () => 'with conflict: 0';
218+
219+
expect(faker.unique(method, [], { store })).toBe('with conflict: 0');
220+
expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' });
221+
222+
expect(() => faker.unique(method, [], { store })).toThrow();
223+
224+
delete store['with conflict: 0'];
225+
226+
expect(faker.unique(method, [], { store })).toBe('with conflict: 0');
227+
expect(store).toEqual({ 'with conflict: 0': 'with conflict: 0' });
228+
});
213229
});

0 commit comments

Comments
 (0)