diff --git a/src/internal/faker-for-core.ts b/src/internal/faker-for-core.ts new file mode 100644 index 00000000000..1549eb6a1e7 --- /dev/null +++ b/src/internal/faker-for-core.ts @@ -0,0 +1,27 @@ +import type { FakerCore } from '../core'; +import { Faker } from '../faker'; + +const fakers = new WeakMap(); + +/** + * Returns a `Faker` instance that uses the given `FakerCore`. + * + * The instance is cached per `FakerCore`, because creating a new `Faker` is expensive + * and all state (locale, randomizer, config) lives in the `FakerCore` anyway. + * + * @internal + * + * @param fakerCore The FakerCore to use. + * + * @example + * getFakerForCore(fakerCore).helpers.fake('{{person.lastName}}'); + */ +export function getFakerForCore(fakerCore: FakerCore): Faker { + let faker = fakers.get(fakerCore); + if (faker == null) { + faker = new Faker(fakerCore); + fakers.set(fakerCore, faker); + } + + return faker; +} diff --git a/src/modules/commerce/product-description.ts b/src/modules/commerce/product-description.ts index b7b6e8aed43..88b78cc0269 100644 --- a/src/modules/commerce/product-description.ts +++ b/src/modules/commerce/product-description.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Returns a product description. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeCommerceProductDescription(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.commerce.product_description ); } diff --git a/src/modules/commerce/product-name.ts b/src/modules/commerce/product-name.ts index 4440e05f36a..b2f081af1e3 100644 --- a/src/modules/commerce/product-name.ts +++ b/src/modules/commerce/product-name.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random descriptive product name. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeCommerceProductName(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.commerce.product_name.pattern ); } diff --git a/src/modules/company/name.ts b/src/modules/company/name.ts index 354a636d5c2..a5bb0bba921 100644 --- a/src/modules/company/name.ts +++ b/src/modules/company/name.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random company name. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeCompanyName(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.company.name_pattern ); } diff --git a/src/modules/finance/transaction-description.ts b/src/modules/finance/transaction-description.ts index a9f6217619f..29d3ae5d83a 100644 --- a/src/modules/finance/transaction-description.ts +++ b/src/modules/finance/transaction-description.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random transaction description. @@ -17,7 +17,7 @@ import { Faker } from '../../faker'; export function fakeFinanceTransactionDescription( fakerCore: FakerCore ): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.finance.transaction_description_pattern ); } diff --git a/src/modules/food/description.ts b/src/modules/food/description.ts index 459c49021a6..b9871382a80 100644 --- a/src/modules/food/description.ts +++ b/src/modules/food/description.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random dish description. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeFoodDescription(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.food.description_pattern ); } diff --git a/src/modules/food/dish.ts b/src/modules/food/dish.ts index d43ba3cc3be..cfee11af406 100644 --- a/src/modules/food/dish.ts +++ b/src/modules/food/dish.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; import { fakeDatatypeBoolean } from '../datatype/boolean'; import { fakeHelpersArrayElement } from '../helpers/array-element'; @@ -31,7 +31,9 @@ export function fakeFoodDish(fakerCore: FakerCore): string { // A 50/50 mix of specific dishes and dish_patterns if (fakeDatatypeBoolean(fakerCore)) { return toTitleCase( - new Faker(fakerCore).helpers.fake(fakerCore.locale.food.dish_pattern) + getFakerForCore(fakerCore).helpers.fake( + fakerCore.locale.food.dish_pattern + ) ); } diff --git a/src/modules/hacker/phrase.ts b/src/modules/hacker/phrase.ts index a1fa3d9a6d0..e15d13068d8 100644 --- a/src/modules/hacker/phrase.ts +++ b/src/modules/hacker/phrase.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random hacker/IT phrase. @@ -15,5 +15,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeHackerPhrase(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake(fakerCore.locale.hacker.phrase); + return getFakerForCore(fakerCore).helpers.fake( + fakerCore.locale.hacker.phrase + ); } diff --git a/src/modules/internet/user-agent.ts b/src/modules/internet/user-agent.ts index f0ab6724f0e..abd6c7bc80a 100644 --- a/src/modules/internet/user-agent.ts +++ b/src/modules/internet/user-agent.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random user agent string. @@ -15,7 +15,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeInternetUserAgent(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.internet.user_agent_pattern ); } diff --git a/src/modules/location/city.ts b/src/modules/location/city.ts index 338d783003c..0971cd78cdc 100644 --- a/src/modules/location/city.ts +++ b/src/modules/location/city.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random localized city name. @@ -15,7 +15,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeLocationCity(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.location.city_pattern ); } diff --git a/src/modules/location/postal-address.ts b/src/modules/location/postal-address.ts index 381d48d5a8f..1f9c5583e5f 100644 --- a/src/modules/location/postal-address.ts +++ b/src/modules/location/postal-address.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random localized full postal address, which may include a street address, secondary address, city, state, and zip code. To ensure you get locale-specific address formats, use a localized Faker instance. @@ -32,7 +32,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeLocationPostalAddress(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.location.postal_address ); } diff --git a/src/modules/location/secondary-address.ts b/src/modules/location/secondary-address.ts index 82721c67b07..1aba3f1b7d8 100644 --- a/src/modules/location/secondary-address.ts +++ b/src/modules/location/secondary-address.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; import { fakeStringNumeric } from '../string/numeric'; /** @@ -16,8 +16,8 @@ import { fakeStringNumeric } from '../string/numeric'; * @experimental */ export function fakeLocationSecondaryAddress(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers - .fake(fakerCore.locale.location.secondary_address) + return getFakerForCore(fakerCore) + .helpers.fake(fakerCore.locale.location.secondary_address) .replaceAll(/#+/g, (m) => fakeStringNumeric(fakerCore, { length: m.length, diff --git a/src/modules/location/street-address.ts b/src/modules/location/street-address.ts index 7e752d544a0..a45f9e3fb0b 100644 --- a/src/modules/location/street-address.ts +++ b/src/modules/location/street-address.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random localized street address. @@ -40,5 +40,5 @@ export function fakeLocationStreetAddress( const formats = fakerCore.locale.location.street_address; const format = formats[useFullAddress ? 'full' : 'normal']; - return new Faker(fakerCore).helpers.fake(format); + return getFakerForCore(fakerCore).helpers.fake(format); } diff --git a/src/modules/location/street.ts b/src/modules/location/street.ts index 560c541d924..4b3d8b83f93 100644 --- a/src/modules/location/street.ts +++ b/src/modules/location/street.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random localized street name. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakeLocationStreet(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.location.street_pattern ); } diff --git a/src/modules/location/zip-code.ts b/src/modules/location/zip-code.ts index 46a0376c0bc..ed61f2c55a0 100644 --- a/src/modules/location/zip-code.ts +++ b/src/modules/location/zip-code.ts @@ -1,6 +1,6 @@ import type { FakerCore } from '../../core'; import { FakerError } from '../../errors/faker-error'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; import { fakeHelpersArrayElement } from '../helpers/array-element'; import { fakeHelpersReplaceSymbols } from '../helpers/replace-symbols'; @@ -62,7 +62,7 @@ export function fakeLocationZipCode( throw new FakerError(`No zip code definition found for state "${state}"`); } - return new Faker(fakerCore).helpers.fake(zipPattern); + return getFakerForCore(fakerCore).helpers.fake(zipPattern); } let { format = fakerCore.locale.location.postcode } = options; diff --git a/src/modules/person/bio.ts b/src/modules/person/bio.ts index 1b17f02950f..fae636f6397 100644 --- a/src/modules/person/bio.ts +++ b/src/modules/person/bio.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Returns a random short biography @@ -14,5 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakePersonBio(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake(fakerCore.locale.person.bio_pattern); + return getFakerForCore(fakerCore).helpers.fake( + fakerCore.locale.person.bio_pattern + ); } diff --git a/src/modules/person/job-title.ts b/src/modules/person/job-title.ts index 8f650b5c755..7c2149bf020 100644 --- a/src/modules/person/job-title.ts +++ b/src/modules/person/job-title.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; /** * Generates a random job title. @@ -14,7 +14,7 @@ import { Faker } from '../../faker'; * @experimental */ export function fakePersonJobTitle(fakerCore: FakerCore): string { - return new Faker(fakerCore).helpers.fake( + return getFakerForCore(fakerCore).helpers.fake( fakerCore.locale.person.job_title_pattern ); } diff --git a/src/modules/person/last-name.ts b/src/modules/person/last-name.ts index cf1a9fb6661..84dafa769d6 100644 --- a/src/modules/person/last-name.ts +++ b/src/modules/person/last-name.ts @@ -1,5 +1,5 @@ import type { FakerCore } from '../../core'; -import { Faker } from '../../faker'; +import { getFakerForCore } from '../../internal/faker-for-core'; import { fakeHelpersArrayElement } from '../helpers/array-element'; import { fakeHelpersWeightedArrayElement } from '../helpers/weighted-array-element'; import { selectDefinition } from './_select-definition'; @@ -31,7 +31,7 @@ export function fakePersonLastName( fakerCore, selectDefinition(fakerCore, sex, patterns) ); - return new Faker(fakerCore).helpers.fake(pattern); + return getFakerForCore(fakerCore).helpers.fake(pattern); } return fakeHelpersArrayElement( diff --git a/test/internal/faker-for-core.spec.ts b/test/internal/faker-for-core.spec.ts new file mode 100644 index 00000000000..95b80671e7c --- /dev/null +++ b/test/internal/faker-for-core.spec.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from 'vitest'; +import { Faker, createFakerCore, en } from '../../src'; +import { getFakerForCore } from '../../src/internal/faker-for-core'; +import { fakePersonLastName } from '../../src/modules/person'; + +describe('internal', () => { + describe('getFakerForCore', () => { + it('should return the same instance for the same core', () => { + const fakerCore = createFakerCore({ locale: en }); + + expect(getFakerForCore(fakerCore)).toBe(getFakerForCore(fakerCore)); + }); + + it('should return different instances for different cores', () => { + const fakerCore1 = createFakerCore({ locale: en }); + const fakerCore2 = createFakerCore({ locale: en }); + + expect(getFakerForCore(fakerCore1)).not.toBe(getFakerForCore(fakerCore2)); + }); + + it('should share the randomizer and locale of the core', () => { + const fakerCore = createFakerCore({ locale: en }); + const faker = getFakerForCore(fakerCore); + + expect(faker.fakerCore.randomizer).toBe(fakerCore.randomizer); + expect(faker.fakerCore.locale).toBe(fakerCore.locale); + expect(faker.fakerCore.config).toBe(fakerCore.config); + }); + + it('should generate the same values as a fresh Faker', () => { + const pattern = '{{person.lastName}} {{location.city}}'; + const fakerCore = createFakerCore({ locale: en, seed: 42 }); + const expected = [1, 2, 3].map(() => + new Faker(fakerCore).helpers.fake(pattern) + ); + + fakerCore.randomizer.seed(42); + const actual = [1, 2, 3].map(() => + getFakerForCore(fakerCore).helpers.fake(pattern) + ); + + expect(actual).toEqual(expected); + }); + + it('should be used by standalone functions', () => { + const fakerCore = createFakerCore({ locale: en }); + const fake = vi.fn<() => string>(() => 'cached'); + getFakerForCore(fakerCore).helpers.fake = fake; + + expect(fakePersonLastName(fakerCore)).toBe('cached'); + expect(fakePersonLastName(fakerCore)).toBe('cached'); + expect(fake).toHaveBeenCalledTimes(2); + }); + }); +});