diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000000..5b3084290b7 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,20 @@ +/** + * The possible configuration options, that can be set. + * This type exists to be extended for plugins via type augmentation. + * + * The `@default` tag is used to indicate the default value, that should be used if, the config is absent. + */ +export interface FakerConfig { + /** + * The function used to generate the `refDate` date instance, if not provided as method param. + * The function must return a new valid `Date` instance for every call. + * + * @see [Reproducible Results](https://fakerjs.dev/guide/usage.html#reproducible-results) + * @see faker.seed(): For generating reproducible values. + * + * @since 9.0.0 + * + * @default () => new Date() + */ + defaultRefDate?: () => Date; +} diff --git a/src/core.ts b/src/core.ts new file mode 100644 index 00000000000..3609fd65f0a --- /dev/null +++ b/src/core.ts @@ -0,0 +1,25 @@ +import type { FakerConfig } from './config'; +import type { LocaleDefinition } from './definitions'; +import type { Randomizer } from './randomizer'; + +/** + * The core grants access to the locale data, the randomizer and config settings. + */ +export interface FakerCore { + /** + * The locale data associated with this instance. + * + * Always present, but it might be empty if the locale data is not available. + */ + readonly locale: LocaleDefinition; + + /** + * The randomizer used to generate random values. + */ + readonly randomizer: Randomizer; + + /** + * The configuration settings used by this instance. + */ + readonly config: FakerConfig; +} diff --git a/src/faker.ts b/src/faker.ts index 77630d29ee7..627a628ae73 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -1,8 +1,8 @@ +import type { FakerCore } from './core'; import type { LocaleDefinition, MetadataDefinition } from './definitions'; import { FakerError } from './errors/faker-error'; import { deprecated } from './internal/deprecated'; -import type { LocaleProxy } from './internal/locale-proxy'; -import { createLocaleProxy } from './internal/locale-proxy'; +import { createLocaleProxy, type LocaleProxy } from './internal/locale-proxy'; import { AirlineModule } from './modules/airline'; import { AnimalModule } from './modules/animal'; import { BookModule } from './modules/book'; @@ -32,6 +32,7 @@ import { WordModule } from './modules/word'; import type { Randomizer } from './randomizer'; import { SimpleFaker } from './simple-faker'; import { mergeLocales } from './utils/merge-locales'; +import { generateMersenne53Randomizer } from './utils/mersenne'; /** * This is Faker's main class containing all modules that can be used to generate data. @@ -59,7 +60,6 @@ import { mergeLocales } from './utils/merge-locales'; * customFaker.music.genre(); // throws Error as this data is not available in `es` */ export class Faker extends SimpleFaker { - readonly rawDefinitions: LocaleDefinition; readonly definitions: LocaleProxy; readonly airline: AirlineModule = new AirlineModule(this); @@ -87,6 +87,11 @@ export class Faker extends SimpleFaker { readonly vehicle: VehicleModule = new VehicleModule(this); readonly word: WordModule = new WordModule(this); + get rawDefinitions(): LocaleDefinition { + // TODO @ST-DDT 2024-05-14: Should we deprecate this? + return this.fakerCore.locale; + } + // Aliases /** @deprecated Use {@link Faker#location} instead */ get address(): AddressModule { @@ -140,27 +145,41 @@ export class Faker extends SimpleFaker { * * @since 8.0.0 */ - constructor(options: { - /** - * The locale data to use for this instance. - * If an array is provided, the first locale that has a definition for a given property will be used. - * - * @see mergeLocales(): For more information about how the locales are merged. - */ - locale: LocaleDefinition | LocaleDefinition[]; + constructor( + options: + | { + /** + * The locale data to use for this instance. + * If an array is provided, the first locale that has a definition for a given property will be used. + * + * @see mergeLocales(): For more information about how the locales are merged. + */ + locale: LocaleDefinition | LocaleDefinition[]; - /** - * The Randomizer to use. - * Specify this only if you want to use it to achieve a specific goal, - * such as sharing the same random generator with other instances/tools. - * - * @default generateMersenne53Randomizer() - */ + /** + * The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * + * @default generateMersenne53Randomizer() + */ + randomizer?: Randomizer; + } + | { + /** + * The faker core with the randomizer, locale data and config to use. + */ + fakerCore: FakerCore; + } + ); + constructor(options: { + locale?: LocaleDefinition | LocaleDefinition[]; randomizer?: Randomizer; + fakerCore?: FakerCore; }) { - super({ randomizer: options.randomizer }); + super(options); - let { locale } = options; + let { locale = {} } = options; if (Array.isArray(locale)) { if (locale.length === 0) { @@ -172,7 +191,16 @@ export class Faker extends SimpleFaker { locale = mergeLocales(locale); } - this.rawDefinitions = locale; + const { + randomizer = generateMersenne53Randomizer(), + fakerCore = { locale, randomizer, config: {} }, + } = options; + + // TODO @ST-DDT 2024-05-14: Workaround for https://github.com/egoist/tsup/issues/1124 + // @ts-expect-error: fakerCore is not writable + this.fakerCore = fakerCore; + // super({ fakerCore }); + this.definitions = createLocaleProxy(this.rawDefinitions); } @@ -188,7 +216,7 @@ export class Faker extends SimpleFaker { * @since 8.1.0 */ getMetadata(): MetadataDefinition { - return this.rawDefinitions.metadata ?? {}; + return this.fakerCore.locale.metadata ?? {}; } } diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts index be5e213f272..621b0adc78a 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -96,8 +96,7 @@ export class NumberModule extends SimpleModuleBase { throw new FakerError(`Max ${max} should be greater than min ${min}.`); } - // @ts-expect-error: access private member field - const randomizer = this.faker._randomizer; + const { randomizer } = this.faker.fakerCore; const real = randomizer.next(); const delta = effectiveMax - effectiveMin + 1; // +1 for inclusive max bounds and even distribution return Math.floor(real * delta + effectiveMin) * multipleOf; @@ -214,8 +213,7 @@ export class NumberModule extends SimpleModuleBase { return int / factor; } - // @ts-expect-error: access private member field - const randomizer = this.faker._randomizer; + const { randomizer } = this.faker.fakerCore; const real = randomizer.next(); return real * (max - min) + min; } diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 743df923570..6a42036f5e3 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -1,3 +1,4 @@ +import type { FakerCore } from './core'; import { DatatypeModule } from './modules/datatype'; import { SimpleDateModule } from './modules/date'; import { SimpleHelpersModule } from './modules/helpers'; @@ -26,13 +27,13 @@ import { generateMersenne53Randomizer } from './utils/mersenne'; * simpleFaker.string.uuid(); // 'c50e1f5c-86e8-4aa9-888e-168e0a182519' */ export class SimpleFaker { - protected _defaultRefDate: () => Date = () => new Date(); + readonly fakerCore: FakerCore; /** * Gets a new reference date used to generate relative dates. */ get defaultRefDate(): () => Date { - return this._defaultRefDate; + return this.fakerCore.config.defaultRefDate ?? (() => new Date()); } /** @@ -72,15 +73,12 @@ export class SimpleFaker { dateOrSource: string | Date | number | (() => Date) = () => new Date() ): void { if (typeof dateOrSource === 'function') { - this._defaultRefDate = dateOrSource; + this.fakerCore.config.defaultRefDate = dateOrSource; } else { - this._defaultRefDate = () => new Date(dateOrSource); + this.fakerCore.config.defaultRefDate = () => new Date(dateOrSource); } } - /** @internal */ - private readonly _randomizer: Randomizer; - readonly datatype: DatatypeModule = new DatatypeModule(this); readonly date: SimpleDateModule = new SimpleDateModule(this); readonly helpers: SimpleHelpersModule = new SimpleHelpersModule(this); @@ -110,21 +108,37 @@ export class SimpleFaker { * * @since 8.1.0 */ + constructor( + options?: + | { + /** + * The Randomizer to use. + * Specify this only if you want to use it to achieve a specific goal, + * such as sharing the same random generator with other instances/tools. + * + * @default generateMersenne53Randomizer() + */ + randomizer?: Randomizer; + } + | { + /** + * The faker core with the randomizer and config to use. + */ + fakerCore: FakerCore; + } + ); constructor( options: { - /** - * The Randomizer to use. - * Specify this only if you want to use it to achieve a specific goal, - * such as sharing the same random generator with other instances/tools. - * - * @default generateMersenne53Randomizer() - */ randomizer?: Randomizer; + fakerCore?: FakerCore; } = {} ) { - const { randomizer = generateMersenne53Randomizer() } = options; + const { + randomizer = generateMersenne53Randomizer(), + fakerCore = { locale: {}, randomizer, config: {} }, + } = options; - this._randomizer = randomizer; + this.fakerCore = fakerCore; } /** @@ -250,7 +264,7 @@ export class SimpleFaker { seed( seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) ): number | number[] { - this._randomizer.seed(seed); + this.fakerCore.randomizer.seed(seed); return seed; } diff --git a/test/all-functional.spec.ts b/test/all-functional.spec.ts index 33d89312d5e..59ae66fab5e 100644 --- a/test/all-functional.spec.ts +++ b/test/all-functional.spec.ts @@ -3,13 +3,7 @@ import type { Faker, allLocales } from '../src'; import { allFakers, fakerEN } from '../src'; import { keys } from '../src/internal/keys'; -const IGNORED_MODULES = new Set([ - 'rawDefinitions', - 'definitions', - 'helpers', - '_randomizer', - '_defaultRefDate', -]); +const IGNORED_MODULES = new Set(['definitions', 'helpers', 'fakerCore']); function getMethodNamesByModules(faker: Faker): { [module: string]: string[] } { return Object.fromEntries( diff --git a/test/modules/number.spec.ts b/test/modules/number.spec.ts index f9adf9ff07d..474e64515ad 100644 --- a/test/modules/number.spec.ts +++ b/test/modules/number.spec.ts @@ -629,8 +629,7 @@ describe('number', () => { describe('value range tests', () => { const customFaker = new SimpleFaker(); - // @ts-expect-error: access private member field - const randomizer = customFaker._randomizer; + const { randomizer } = customFaker.fakerCore; describe('int', () => { it('should be able to return 0', () => { randomizer.next = () => 0;