Skip to content

Commit

Permalink
refactor(helpers)!: remove v8 deprecated unique (#2661)
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Cheng <ericcheng9316@gmail.com>
Co-authored-by: Matt Mayer <matt@matthewmayer.co.uk>
  • Loading branch information
3 people committed Feb 24, 2024
1 parent fd05126 commit 4382fd9
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 495 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ const config: UserConfig<DefaultTheme.Config> = {
text: 'Randomizer',
link: '/guide/randomizer',
},
{
text: 'Unique Values',
link: '/guide/unique',
},
{
text: 'Upgrading to v9',
link: '/guide/upgrading',
Expand Down
58 changes: 58 additions & 0 deletions docs/guide/unique.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Unique Values

In general, Faker methods do not return unique values.

```ts
faker.seed(55);
faker.animal.type(); //'cat'
faker.animal.type(); //'bird'
faker.animal.type(); //'horse'
faker.animal.type(); //'horse'
```

Some methods and locales use much smaller data sets than others. For example, `faker.animal.type` has only 13 possible animals to choose from. In contrast, `faker.person.fullName()` pulls from a list of hundreds of first names, surnames, and prefixes/suffixes, so it can generate hundreds of thousands of unique names. Even then, the [birthday paradox](https://en.wikipedia.org/wiki/Birthday_Paradox) means that duplicate values will quickly be generated.

Sometimes, you want to generate unique values. For example, you may wish to have unique values in a database email column.
There are a few possible strategies for this:

1. Use `faker.helpers.uniqueArray()` if you want to generate all the values at one time. For example:

```ts
faker.helpers.uniqueArray(faker.internet.email, 1000); // will generate 1000 unique email addresses
```

2. If there are insufficient values for your needs, consider prefixing or suffixing values with your own sequential values, for example you could prefix `1.`, `2.` to each generated email in turn.

3. Build your own logic to keep track of a set of previously generated values and regenerate values as necessary if a duplicate is generated

4. Use a third party package to enforce uniqueness such as [enforce-unique](https://github.com/MansurAliKoroglu/enforce-unique)

Note you can supply a maximum time (in milliseconds) or maximum number of retries.

```js
import { EnforceUniqueError, UniqueEnforcer } from 'enforce-unique';

const uniqueEnforcerEmail = new UniqueEnforcer();

function createRandomUser() {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const email = uniqueEnforcerEmail.enforce(
() =>
faker.internet.email({
firstName,
lastName,
}),
{
maxTime: 50,
maxRetries: 50,
}
);

return {
firstName,
lastName,
email,
};
}
```
65 changes: 65 additions & 0 deletions docs/guide/upgrading_v9/2661.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
### Remove helpers.unique

Prior to v9, Faker provided a [`faker.helpers.unique()`](https://v8.fakerjs.dev/api/helpers.html#unique) method which had a global store to keep track of duplicates. This was removed in v9.

Please see the [unique values guide](/guide/unique) for alternatives.

For example, many simple use cases can use [`faker.helpers.uniqueArray`](https://v8.fakerjs.dev/api/helpers.html#uniqueArray). Or you can migrate to a third party package such as `enforce-unique`:

Basic example:

```js
// OLD
const name = faker.helpers.unique(faker.person.firstName);

// NEW
import { UniqueEnforcer } from 'enforce-unique';
//const { UniqueEnforcer } = require("enforce-unique") // CJS

const enforcerName = new UniqueEnforcer();
const name = enforcerName.enforce(faker.person.firstName);
```

With parameters:

```js
// OLD
const stateCode = faker.helpers.unique(faker.location.state, [
{
abbreviated: true,
},
]);

// NEW
import { UniqueEnforcer } from 'enforce-unique';

const enforcerState = new UniqueEnforcer();
const stateCode = enforcerState.enforce(() =>
faker.location.state({
abbreviated: true,
})
);
```

With options:

```js
// OLD
const city = faker.helpers.unique(faker.location.city, [], {
maxRetries: 100,
maxTime: 1000,
});

// NEW
import { UniqueEnforcer } from 'enforce-unique';

const enforcer = new UniqueEnforcer();
const city = enforcer.enforce(faker.location.city, {
maxRetries: 100,
maxTime: 1000,
});
```

::: tip Note
`enforce-unique` does not support the `exclude` or `store` options. If you were previously using these, you may wish to build your own unique logic instead.
:::
41 changes: 1 addition & 40 deletions docs/guide/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,45 +233,6 @@ Here, we could also pass in the `sex` value as argument, but in our use-case the
By doing this first, we are able to pass both names into the `email` generation function.
This allows the value to be more reasonable based on the provided arguments.

But we can take this even another step further.
Opposite to the `_id` property that uses an `uuid` implementation, which is unique by design, the `email` property potentially isn't.
But, in most use-cases, this would be desirable.

Faker has your back, with another helper method:

```ts {7-10}
import { faker } from '@faker-js/faker';

function createRandomUser(): User {
const sex = faker.person.sexType();
const firstName = faker.person.firstName(sex);
const lastName = faker.person.lastName();
const email = faker.helpers.unique(faker.internet.email, [
firstName,
lastName,
]);

return {
_id: faker.string.uuid(),
avatar: faker.image.avatar(),
birthday: faker.date.birthdate(),
email,
firstName,
lastName,
sex,
subscriptionTier: faker.helpers.arrayElement(['free', 'basic', 'business']),
};
}

const user = createRandomUser();
```

By wrapping Faker's `email` function with the [`unique`](../api/helpers.md#unique) helper function, we ensure that the return value of `email` is always unique.

::: warning
The `faker.helpers.unique` is targeted to be removed from Faker in the future.
Please have a look at the issue [#1785](https://github.com/faker-js/faker/issues/1785).
We will update these docs once a replacement is available.
:::
Unlike the `_id` property that uses an `uuid` implementation, which has a low chance of duplicates, the `email` function is more likely to produce duplicates, especially if the call arguments are similar. We have a dedicated guide page on generating [unique values](unique).

Congratulations, you should now be able to create any complex object you desire. Happy faking 🥳.
118 changes: 0 additions & 118 deletions src/modules/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { deprecated } from '../../internal/deprecated';
import { SimpleModuleBase } from '../../internal/module-base';
import { fakeEval } from './eval';
import { luhnCheckValue } from './luhn-check';
import type { RecordKey } from './unique';
import * as uniqueExec from './unique';

/**
* Returns a number based on given RegEx-based quantifier symbol or quantifier values.
Expand Down Expand Up @@ -204,14 +202,6 @@ export function legacyReplaceSymbolWithNumber(
* Module with various helper methods providing basic (seed-dependent) operations useful for implementing faker methods (without methods requiring localized data).
*/
export class SimpleHelpersModule extends SimpleModuleBase {
/**
* Global store of unique values.
* This means that faker should *never* return duplicate values across all API methods when using `faker.helpers.unique` without passing `options.store`.
*
* @internal
*/
private readonly uniqueStore: Record<RecordKey, RecordKey> = {};

/**
* Slugifies the given string.
* For that all spaces (` `) are replaced by hyphens (`-`)
Expand Down Expand Up @@ -1129,114 +1119,6 @@ export class SimpleHelpersModule extends SimpleModuleBase {
return this.faker.number.int(numberOrRange);
}

/**
* Generates a unique result using the results of the given method.
* Used unique entries will be stored internally and filtered from subsequent calls.
*
* @template TMethod The type of the method to execute.
*
* @param method The method used to generate the values.
* @param args The arguments used to call the method. Defaults to `[]`.
* @param options The optional options used to configure this method.
* @param options.startTime This parameter does nothing.
* @param options.maxTime The time in milliseconds this method may take before throwing an error. Defaults to `50`.
* @param options.maxRetries The total number of attempts to try before throwing an error. Defaults to `50`.
* @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.
*
* @see https://github.com/faker-js/faker/issues/1785#issuecomment-1407773744
*
* @example
* faker.helpers.unique(faker.person.firstName) // 'Corbin'
*
* @since 7.5.0
*
* @deprecated Please find a dedicated npm package instead, or even create one on your own if you want to.
* More info can be found in issue [faker-js/faker #1785](https://github.com/faker-js/faker/issues/1785).
*/
unique<
TMethod extends (
// TODO @Shinigami92 2023-02-14: This `any` type can be fixed by anyone if they want to.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...parameters: any[]
) => RecordKey,
>(
method: TMethod,
args: Parameters<TMethod> = [] as unknown as Parameters<TMethod>,
options: {
/**
* This parameter does nothing.
*
* @default new Date().getTime()
*/
startTime?: number;
/**
* The time in milliseconds this method may take before throwing an error.
*
* @default 50
*/
maxTime?: number;
/**
* The total number of attempts to try before throwing an error.
*
* @default 50
*/
maxRetries?: number;
/**
* This parameter does nothing.
*
* @default 0
*/
currentIterations?: number;
/**
* The value or values that should be excluded/skipped.
*
* @default []
*/
exclude?: RecordKey | RecordKey[];
/**
* The function used to determine whether a value was already returned.
*
* Defaults to check the existence of the key.
*
* @default (obj, key) => (obj[key] === undefined ? -1 : 0)
*/
compare?: (obj: Record<RecordKey, RecordKey>, key: RecordKey) => 0 | -1;
/**
* The store of unique entries.
*
* Defaults to a global store.
*/
store?: Record<RecordKey, RecordKey>;
} = {}
): ReturnType<TMethod> {
deprecated({
deprecated: 'faker.helpers.unique',
proposed:
'https://github.com/faker-js/faker/issues/1785#issuecomment-1407773744',
since: '8.0',
until: '9.0',
});

const {
maxTime = 50,
maxRetries = 50,
exclude = [],
store = this.uniqueStore,
} = options;
return uniqueExec.exec(method, args, {
...options,
startTime: Date.now(),
maxTime,
maxRetries,
currentIterations: 0,
exclude,
store,
});
}

/**
* Generates an array containing values returned by the given method.
*
Expand Down

0 comments on commit 4382fd9

Please sign in to comment.