From 97a7163cc27fdac6b370391cb95407e38a470103 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 31 Jul 2023 23:10:13 +0200 Subject: [PATCH 01/21] feat: custom prng --- src/faker.ts | 32 ++++++++++--- .../{mersenne/twister.ts => mersenne.ts} | 28 +++++++++++- src/internal/mersenne/mersenne.ts | 45 ------------------- src/modules/number/index.ts | 13 +++--- src/prng.ts | 17 +++++++ test/all_functional.spec.ts | 2 +- test/faker.spec.ts | 16 +++++++ test/mersenne.spec.ts | 14 +++--- 8 files changed, 101 insertions(+), 66 deletions(-) rename src/internal/{mersenne/twister.ts => mersenne.ts} (94%) delete mode 100644 src/internal/mersenne/mersenne.ts create mode 100644 src/prng.ts diff --git a/src/faker.ts b/src/faker.ts index 9161497e280..319c3c3270a 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -1,8 +1,7 @@ import type { LocaleDefinition, MetadataDefinition } from './definitions'; import { FakerError } from './errors/faker-error'; import { deprecated } from './internal/deprecated'; -import type { Mersenne } from './internal/mersenne/mersenne'; -import mersenne from './internal/mersenne/mersenne'; +import { newMersennePRNG } from './internal/mersenne'; import type { LocaleProxy } from './locale-proxy'; import { createLocaleProxy } from './locale-proxy'; import { AirlineModule } from './modules/airline'; @@ -33,6 +32,7 @@ import { StringModule } from './modules/string'; import { SystemModule } from './modules/system'; import { VehicleModule } from './modules/vehicle'; import { WordModule } from './modules/word'; +import type { PRNG } from './prng'; import { mergeLocales } from './utils/merge-locales'; /** @@ -114,7 +114,7 @@ export class Faker { } /** @internal */ - private readonly _mersenne: Mersenne = mersenne(); + private readonly _prng: PRNG; /** * @deprecated Use the modules specific to the type of data you want to generate instead. @@ -184,6 +184,8 @@ export class Faker { * * @param options The options to use. * @param options.locale The locale data to use. + * @param options.prng The PRNG to use. Defaults to faker's Mersenne Twister based PRNG. + * Only overwrite this if you want to reuse the same PRNG in different libraries to ensure reproducible results across all of them. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -205,6 +207,14 @@ export class Faker { * @see mergeLocales */ locale: LocaleDefinition | LocaleDefinition[]; + + /** + * The PRNG to use. Defaults to faker's Mersenne Twister based PRNG. + * Only overwrite this if you want to reuse the same PRNG in different libraries to ensure reproducible results across all of them. + * + * @default newMersennePRNG() + */ + prng?: PRNG; }); /** * Creates a new instance of Faker. @@ -241,6 +251,8 @@ export class Faker { * @param options.locale The locale data to use or the name of the main locale. * @param options.locales The locale data to use. * @param options.localeFallback The name of the fallback locale to use. + * @param options.prng The PRNG to use. Defaults to faker's Mersenne Twister based PRNG. + * Only overwrite this if you want to reuse the same PRNG in different libraries to ensure reproducible results across all of them. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -264,6 +276,14 @@ export class Faker { * @see mergeLocales */ locale: LocaleDefinition | LocaleDefinition[]; + + /** + * The PRNG to use. Defaults to faker's Mersenne Twister based PRNG. + * Only overwrite this if you want to reuse the same PRNG in different libraries to ensure reproducible results across all of them. + * + * @default newMersennePRNG() + */ + prng?: PRNG; } | { /** @@ -292,11 +312,12 @@ export class Faker { ); constructor( options: - | { locale: LocaleDefinition | LocaleDefinition[] } + | { locale: LocaleDefinition | LocaleDefinition[]; prng?: PRNG } | { locales: Record; locale?: string; localeFallback?: string; + prng?: PRNG; } ) { const { locales } = options as { @@ -332,6 +353,7 @@ export class Faker { locale = mergeLocales(locale); } + this._prng = options.prng ?? newMersennePRNG(); this.rawDefinitions = locale as LocaleDefinition; this.definitions = createLocaleProxy(this.rawDefinitions); } @@ -453,7 +475,7 @@ export class Faker { seed( seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) ): number | number[] { - this._mersenne.seed(seed); + this._prng.seed(seed); return seed; } diff --git a/src/internal/mersenne/twister.ts b/src/internal/mersenne.ts similarity index 94% rename from src/internal/mersenne/twister.ts rename to src/internal/mersenne.ts index d25e3acf583..9fc89e75ae2 100644 --- a/src/internal/mersenne/twister.ts +++ b/src/internal/mersenne.ts @@ -1,3 +1,5 @@ +import type { PRNG } from '../prng'; + /** * Copyright (c) 2022-2023 Faker * @@ -71,7 +73,7 @@ * * @internal */ -export default class MersenneTwister19937 { +class MersenneTwister19937 { private readonly N = 624; private readonly M = 397; private readonly MATRIX_A = 0x9908b0df; // constant vector a @@ -323,3 +325,27 @@ export default class MersenneTwister19937 { } // These real versions are due to Isaku Wada, 2002/01/09 } + +/** + * Generate seed based random numbers. + * + * @internal + */ +export function newMersennePRNG(): PRNG { + const twister = new MersenneTwister19937(); + + twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + + return { + next(): number { + return twister.genrandReal2(); + }, + seed(seed: number | number[]): void { + if (typeof seed === 'number') { + twister.initGenrand(seed); + } else if (Array.isArray(seed)) { + twister.initByArray(seed, seed.length); + } + }, + }; +} diff --git a/src/internal/mersenne/mersenne.ts b/src/internal/mersenne/mersenne.ts deleted file mode 100644 index c823af7e2d2..00000000000 --- a/src/internal/mersenne/mersenne.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Twister from './twister'; - -/** - * Generate seed based random numbers. - * - * @internal - */ -export interface Mersenne { - /** - * Generates a random float between `[0, 1)`. - * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol) - */ - next(): number; - - /** - * Sets the seed to use. - * - * @param seed The seed to use. - */ - seed(seed: number | number[]): void; -} - -/** - * Generate seed based random numbers. - * - * @internal - */ -export default function mersenne(): Mersenne { - const twister = new Twister(); - - twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); - - return { - next(): number { - return twister.genrandReal2(); - }, - seed(seed: number | number[]): void { - if (typeof seed === 'number') { - twister.initGenrand(seed); - } else if (Array.isArray(seed)) { - twister.initByArray(seed, seed.length); - } - }, - }; -} diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts index 9472fb82e02..3a21bf041c0 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -1,7 +1,7 @@ import type { Faker } from '../..'; import { FakerError } from '../../errors/faker-error'; import { bindThisToMemberFunctions } from '../../internal/bind-this-to-member-functions'; -import type { Mersenne } from '../../internal/mersenne/mersenne'; +import type { PRNG } from '../../prng'; /** * Module to generate numbers of any kind. @@ -83,10 +83,9 @@ export class NumberModule { throw new FakerError(`Max ${max} should be greater than min ${min}.`); } - const mersenne: Mersenne = - // @ts-expect-error: access private member field - this.faker._mersenne; - const real = mersenne.next(); + // @ts-expect-error: access private member field + const { _prng: prng }: PRNG = this.faker; + const real = prng.next(); return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin); } @@ -162,8 +161,8 @@ export class NumberModule { } // @ts-expect-error: access private member field - const mersenne: Mersenne = this.faker._mersenne; - const real = mersenne.next(); + const { _prng: prng }: PRNG = this.faker; + const real = prng.next(); return real * (max - min) + min; } diff --git a/src/prng.ts b/src/prng.ts new file mode 100644 index 00000000000..38ab0a31342 --- /dev/null +++ b/src/prng.ts @@ -0,0 +1,17 @@ +/** + * Interface for a pseudo-random number generator. + */ +export interface PRNG { + /** + * Generates a random float between `[0, 1)`. + * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol) + */ + next(): number; + + /** + * Sets the seed to use. + * + * @param seed The seed to use. + */ + seed(seed: number | number[]): void; +} diff --git a/test/all_functional.spec.ts b/test/all_functional.spec.ts index ec3bcd40b2d..f48a620459f 100644 --- a/test/all_functional.spec.ts +++ b/test/all_functional.spec.ts @@ -6,7 +6,7 @@ const IGNORED_MODULES = [ 'rawDefinitions', 'definitions', 'helpers', - '_mersenne', + '_prng', '_defaultRefDate', ]; diff --git a/test/faker.spec.ts b/test/faker.spec.ts index 7a305c94788..d48e1ff3450 100644 --- a/test/faker.spec.ts +++ b/test/faker.spec.ts @@ -68,6 +68,22 @@ describe('faker', () => { }); }); + describe('prng', () => { + it('should be possible to provide a custom prng', () => { + const customFaker = new Faker({ + locale: {}, + prng: { + next: () => 0, + seed: () => void 0, + }, + }); + + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + expect(customFaker.number.int()).toBe(0); + }); + }); + // This is only here for coverage // The actual test is in mersenne.spec.ts describe('seed()', () => { diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts index 11b174f4705..0b6fddb4b4e 100644 --- a/test/mersenne.spec.ts +++ b/test/mersenne.spec.ts @@ -1,23 +1,23 @@ import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import type { Mersenne } from '../src/internal/mersenne/mersenne'; -import mersenneFn from '../src/internal/mersenne/mersenne'; +import { newMersennePRNG } from '../src/internal/mersenne'; +import type { PRNG } from '../src/prng'; import { seededRuns } from './support/seededRuns'; import { times } from './support/times'; const NON_SEEDED_BASED_RUN = 25; describe('mersenne twister', () => { - const mersenne: Mersenne = mersenneFn(); + const prng: PRNG = newMersennePRNG(); describe.each( [...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v]) )('seed: %j', (seed) => { beforeEach(() => { - mersenne.seed(seed); + prng.seed(seed); }); it('should return deterministic value for next()', () => { - const actual = mersenne.next(); + const actual = prng.next(); expect(actual).toMatchSnapshot(); }); @@ -35,12 +35,12 @@ describe('mersenne twister', () => { ]) )('random seeded tests %j', (seed) => { beforeAll(() => { - mersenne.seed(seed); + prng.seed(seed); }); describe('next', () => { it('should return random number from interval [0, 1)', () => { - const actual = mersenne.next(); + const actual = prng.next(); expect(actual).toBeGreaterThanOrEqual(0); expect(actual).toBeLessThan(1); From 8a67fc8f1f5096787e4df0a043fb6f0e84dbce54 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 6 Aug 2023 14:41:26 +0200 Subject: [PATCH 02/21] chore: rename generateMersennePRNG --- src/faker.ts | 4 ++-- src/internal/mersenne.ts | 2 +- test/mersenne.spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index 319c3c3270a..58927a70109 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -1,7 +1,7 @@ import type { LocaleDefinition, MetadataDefinition } from './definitions'; import { FakerError } from './errors/faker-error'; import { deprecated } from './internal/deprecated'; -import { newMersennePRNG } from './internal/mersenne'; +import { generateMersennePRNG } from './internal/mersenne'; import type { LocaleProxy } from './locale-proxy'; import { createLocaleProxy } from './locale-proxy'; import { AirlineModule } from './modules/airline'; @@ -353,7 +353,7 @@ export class Faker { locale = mergeLocales(locale); } - this._prng = options.prng ?? newMersennePRNG(); + this._prng = options.prng ?? generateMersennePRNG(); this.rawDefinitions = locale as LocaleDefinition; this.definitions = createLocaleProxy(this.rawDefinitions); } diff --git a/src/internal/mersenne.ts b/src/internal/mersenne.ts index 9fc89e75ae2..775892b79ec 100644 --- a/src/internal/mersenne.ts +++ b/src/internal/mersenne.ts @@ -331,7 +331,7 @@ class MersenneTwister19937 { * * @internal */ -export function newMersennePRNG(): PRNG { +export function generateMersennePRNG(): PRNG { const twister = new MersenneTwister19937(); twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts index 0b6fddb4b4e..b6f8e2123b1 100644 --- a/test/mersenne.spec.ts +++ b/test/mersenne.spec.ts @@ -1,5 +1,5 @@ import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { newMersennePRNG } from '../src/internal/mersenne'; +import { generateMersennePRNG } from '../src/internal/mersenne'; import type { PRNG } from '../src/prng'; import { seededRuns } from './support/seededRuns'; import { times } from './support/times'; @@ -7,7 +7,7 @@ import { times } from './support/times'; const NON_SEEDED_BASED_RUN = 25; describe('mersenne twister', () => { - const prng: PRNG = newMersennePRNG(); + const prng: PRNG = generateMersennePRNG(); describe.each( [...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v]) From 646ef438cc7101214ed37125d7aa1d8cc8e1aa1a Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Tue, 19 Sep 2023 21:52:08 +0200 Subject: [PATCH 03/21] chore: cleanup --- src/modules/number/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts index db12d0f82f0..3ef3cb97aaa 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -1,7 +1,6 @@ import type { SimpleFaker } from '../..'; import { FakerError } from '../../errors/faker-error'; import { bindThisToMemberFunctions } from '../../internal/bind-this-to-member-functions'; -import type { Randomizer } from '../../randomizer'; /** * Module to generate numbers of any kind. @@ -84,7 +83,7 @@ export class NumberModule { } // @ts-expect-error: access private member field - const { randomizer }: Randomizer = this.faker; + const randomizer = this.faker.randomizer; const real = randomizer.next(); return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin); } @@ -159,7 +158,7 @@ export class NumberModule { } // @ts-expect-error: access private member field - const { randomizer }: Randomizer = this.faker; + const randomizer = this.faker.randomizer; const real = randomizer.next(); return real * (max - min) + min; } From 4ca657c530cfc4980c1a24a57f3c365b7eb45f2b Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Thu, 21 Sep 2023 14:56:50 +0200 Subject: [PATCH 04/21] docs: enhance docs --- docs/.vitepress/api-pages.ts | 1 + scripts/apidoc/fakerClass.ts | 34 ++++++++++++++++++------- scripts/apidoc/generate.ts | 3 ++- src/faker.ts | 8 ++---- src/index.ts | 1 + src/internal/mersenne.ts | 2 +- src/modules/number/index.ts | 4 +-- src/randomizer.ts | 48 +++++++++++++++++++++++++++++++++++- src/simple-faker.ts | 14 +++++------ test/all_functional.spec.ts | 2 +- test/mersenne.spec.ts | 4 +-- 11 files changed, 90 insertions(+), 31 deletions(-) diff --git a/docs/.vitepress/api-pages.ts b/docs/.vitepress/api-pages.ts index ae820604eaf..57966ef453e 100644 --- a/docs/.vitepress/api-pages.ts +++ b/docs/.vitepress/api-pages.ts @@ -4,6 +4,7 @@ export const apiPages = [ { text: 'Overview', link: '/api/' }, { text: 'Faker', link: '/api/faker.html' }, { text: 'SimpleFaker', link: '/api/simpleFaker.html' }, + { text: 'Randomizer', link: '/api/randomizer.html' }, { text: 'Airline', link: '/api/airline.html' }, { text: 'Animal', link: '/api/animal.html' }, { text: 'Color', link: '/api/color.html' }, diff --git a/scripts/apidoc/fakerClass.ts b/scripts/apidoc/fakerClass.ts index d7206cf8302..97c95f3150b 100644 --- a/scripts/apidoc/fakerClass.ts +++ b/scripts/apidoc/fakerClass.ts @@ -21,23 +21,33 @@ export async function processFakerClasses( return Promise.all(fakerClasses.map(processClass)); } +export async function processFakerRandomizer( + project: ProjectReflection +): Promise { + const randomizerClass = project + .getChildrenByKind(ReflectionKind.Interface) + .filter((clazz) => clazz.name === 'Randomizer')[0]; + + return processClass(randomizerClass); +} + async function processClass( - fakerClass: DeclarationReflection + clazz: DeclarationReflection ): Promise { - const { name } = fakerClass; - const moduleFieldName = extractModuleFieldName(fakerClass); + const { name } = clazz; + const moduleFieldName = extractModuleFieldName(clazz); console.log(`Processing ${name} class`); - const { comment, deprecated, examples } = analyzeModule(fakerClass); + const { comment, deprecated, examples } = analyzeModule(clazz); const methods: Method[] = []; - console.debug(`- constructor`); - methods.push(await processConstructor(fakerClass)); + if (hasConstructor(clazz)) { + console.debug(`- constructor`); + methods.push(await processConstructor(clazz)); + } - methods.push( - ...(await processModuleMethods(fakerClass, `${moduleFieldName}.`)) - ); + methods.push(...(await processModuleMethods(clazz, `${moduleFieldName}.`))); return writeApiDocsModule( name, @@ -49,6 +59,12 @@ async function processClass( ); } +function hasConstructor(fakerClass: DeclarationReflection): boolean { + return fakerClass + .getChildrenByKind(ReflectionKind.Constructor) + .some((constructor) => constructor.signatures.length > 0); +} + async function processConstructor( fakerClass: DeclarationReflection ): Promise { diff --git a/scripts/apidoc/generate.ts b/scripts/apidoc/generate.ts index d1fefe8f7f5..2d943541892 100644 --- a/scripts/apidoc/generate.ts +++ b/scripts/apidoc/generate.ts @@ -5,7 +5,7 @@ import { writeApiSearchIndex, writeSourceBaseUrl, } from './apiDocsWriter'; -import { processFakerClasses } from './fakerClass'; +import { processFakerClasses, processFakerRandomizer } from './fakerClass'; import { processFakerUtilities } from './fakerUtilities'; import { processModules } from './moduleMethods'; import { loadProject } from './typedoc'; @@ -24,6 +24,7 @@ export async function generate(): Promise { const pages = await Promise.all([ ...(await processFakerClasses(project)), + await processFakerRandomizer(project), ...(await processModules(project)).sort((a, b) => a.text.localeCompare(b.text) ), diff --git a/src/faker.ts b/src/faker.ts index 9e91586605b..998931fd01e 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -125,7 +125,6 @@ export class Faker extends SimpleFaker { * @param options The options to use. * @param options.locale The locale data to use. * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -149,8 +148,7 @@ export class Faker extends SimpleFaker { locale: LocaleDefinition | LocaleDefinition[]; /** - * The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. + * The Randomizer to use. * * @default generateMersenneRandomizer() */ @@ -192,7 +190,6 @@ export class Faker extends SimpleFaker { * @param options.locales The locale data to use. * @param options.localeFallback The name of the fallback locale to use. * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -218,8 +215,7 @@ export class Faker extends SimpleFaker { locale: LocaleDefinition | LocaleDefinition[]; /** - * The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. + * The Randomizer to use. * * @default generateMersenneRandomizer() */ diff --git a/src/index.ts b/src/index.ts index 000541b7dea..8adcd3843c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -128,5 +128,6 @@ export type { StringModule } from './modules/string'; export type { SystemModule } from './modules/system'; export type { VehicleModule } from './modules/vehicle'; export type { WordModule } from './modules/word'; +export type { Randomizer } from './randomizer'; export { SimpleFaker, simpleFaker } from './simple-faker'; export { mergeLocales } from './utils/merge-locales'; diff --git a/src/internal/mersenne.ts b/src/internal/mersenne.ts index 040d0d322ab..d97ec716b88 100644 --- a/src/internal/mersenne.ts +++ b/src/internal/mersenne.ts @@ -331,7 +331,7 @@ class MersenneTwister19937 { * * @internal */ -export function generateMersenneRandomizer(): Randomizer { +export function generateMersenne32Randomizer(): Randomizer { const twister = new MersenneTwister19937(); twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); diff --git a/src/modules/number/index.ts b/src/modules/number/index.ts index 3ef3cb97aaa..9216df7a583 100644 --- a/src/modules/number/index.ts +++ b/src/modules/number/index.ts @@ -83,7 +83,7 @@ export class NumberModule { } // @ts-expect-error: access private member field - const randomizer = this.faker.randomizer; + const randomizer = this.faker._randomizer; const real = randomizer.next(); return Math.floor(real * (effectiveMax + 1 - effectiveMin) + effectiveMin); } @@ -158,7 +158,7 @@ export class NumberModule { } // @ts-expect-error: access private member field - const randomizer = this.faker.randomizer; + const randomizer = this.faker._randomizer; const real = randomizer.next(); return real * (max - min) + min; } diff --git a/src/randomizer.ts b/src/randomizer.ts index f3a35892a28..36b65c47347 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -1,10 +1,49 @@ /** * Interface for a pseudo-random number generator. + * This interface can be used to implement a custom randomizer using third party libraries. + * + *

Created instances are expected to be seeded on creation.

+ * + * @example + * import { FakerError, Randomizer } from '@faker-js/faker'; + * + * function generateMathRandomRandomizer(): Randomizer { + * return { + * next: () => Math.random(), // values aren't reproducible + * seed: () => { + * throw new FakerError('Math.random() Randomizer cannot be seeded'); + * }, + * }; + * } + * + * function generateSimpleRandomizer( + * seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000) + * ): Randomizer { + * const self = { + * next: () => { + * self.state += self.step; + * return (self.state %= 1); + * }, + * seed: (seed) => { + * const value = typeof seed === 'number' ? seed : seed[0]; + * const cleaned = value.toString().replace(/[^\d]+/g, ''); + * self.step = +('1.' + clean) / 2 + 0.26183095653171535; + * self.state = 0.8683615301373573 * self.step + 0.24476001502354827; + * }, + * } as Randomizer & { state: number; step: number }; + * self.seed(seed); + * return self; + * } */ export interface Randomizer { /** * Generates a random float between `[0, 1)`. - * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol) + * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol). + * + * @example + * randomizer.next() // 0.3404027920160495 + * randomizer.next() // 0.929890375900335 + * randomizer.next() // 0.5866362918861691 */ next(): number; @@ -12,6 +51,13 @@ export interface Randomizer { * Sets the seed to use. * * @param seed The seed to use. + * + * @example + * // Random seeds + * randomizer.seed(Date.now() ^ (Math.random() * 0x100000000)); + * // Fixed seeds (for reproducibility) + * randomizer.seed(42); + * randomizer.seed([42, 1337]); */ seed(seed: number | number[]): void; } diff --git a/src/simple-faker.ts b/src/simple-faker.ts index db3d4d118d8..189da204e03 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -1,4 +1,4 @@ -import { generateMersenneRandomizer } from './internal/mersenne'; +import { generateMersenne32Randomizer } from './internal/mersenne'; import { DatatypeModule } from './modules/datatype'; import { SimpleDateModule } from './modules/date'; import { SimpleHelpersModule } from './modules/helpers'; @@ -77,7 +77,7 @@ export class SimpleFaker { } /** @internal */ - private readonly randomizer: Randomizer = generateMersenneRandomizer(); + private readonly _randomizer: Randomizer = generateMersenne32Randomizer(); readonly datatype: DatatypeModule = new DatatypeModule(this); readonly date: SimpleDateModule = new SimpleDateModule(this); @@ -92,22 +92,20 @@ export class SimpleFaker { * * @param options The options to use. * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. */ constructor( options: { /** - * The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. - * Only overwrite this if you want to reuse the same Randomizer in different libraries to ensure reproducible results across all of them. + * The Randomizer to use. * * @default generateMersenneRandomizer() */ randomizer?: Randomizer; } = {} ) { - const { randomizer = generateMersenneRandomizer() } = options; + const { randomizer = generateMersenne32Randomizer() } = options; - this.randomizer = randomizer; + this._randomizer = randomizer; } /** @@ -227,7 +225,7 @@ export class SimpleFaker { seed( seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) ): number | number[] { - this.randomizer.seed(seed); + this._randomizer.seed(seed); return seed; } diff --git a/test/all_functional.spec.ts b/test/all_functional.spec.ts index d1e946725d9..65c9386d14b 100644 --- a/test/all_functional.spec.ts +++ b/test/all_functional.spec.ts @@ -6,7 +6,7 @@ const IGNORED_MODULES = [ 'rawDefinitions', 'definitions', 'helpers', - 'randomizer', + '_randomizer', '_defaultRefDate', ]; diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts index ba09a9df58b..3f40da2c446 100644 --- a/test/mersenne.spec.ts +++ b/test/mersenne.spec.ts @@ -1,5 +1,5 @@ import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; -import { generateMersenneRandomizer } from '../src/internal/mersenne'; +import { generateMersenne32Randomizer } from '../src/internal/mersenne'; import type { Randomizer } from '../src/randomizer'; import { seededRuns } from './support/seededRuns'; import { times } from './support/times'; @@ -7,7 +7,7 @@ import { times } from './support/times'; const NON_SEEDED_BASED_RUN = 25; describe('mersenne twister', () => { - const randomizer: Randomizer = generateMersenneRandomizer(); + const randomizer: Randomizer = generateMersenne32Randomizer(); describe.each( [...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v]) From d40e9e3d9e9133a5889b15f1a2e83569440061e1 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Thu, 21 Sep 2023 14:59:38 +0200 Subject: [PATCH 05/21] chore: rename --- scripts/apidoc/fakerClass.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/scripts/apidoc/fakerClass.ts b/scripts/apidoc/fakerClass.ts index 97c95f3150b..3c47e89c73c 100644 --- a/scripts/apidoc/fakerClass.ts +++ b/scripts/apidoc/fakerClass.ts @@ -59,26 +59,20 @@ async function processClass( ); } -function hasConstructor(fakerClass: DeclarationReflection): boolean { - return fakerClass +function hasConstructor(clazz: DeclarationReflection): boolean { + return clazz .getChildrenByKind(ReflectionKind.Constructor) .some((constructor) => constructor.signatures.length > 0); } async function processConstructor( - fakerClass: DeclarationReflection + clazz: DeclarationReflection ): Promise { - const constructor = fakerClass.getChildrenByKind( - ReflectionKind.Constructor - )[0]; + const constructor = clazz.getChildrenByKind(ReflectionKind.Constructor)[0]; const signature = selectApiSignature(constructor); - const method = await analyzeSignature( - signature, - '', - `new ${fakerClass.name}` - ); + const method = await analyzeSignature(signature, '', `new ${clazz.name}`); return { ...method, From 041160c9b598b14773d2802476292ae17284c7b9 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Thu, 21 Sep 2023 15:01:14 +0200 Subject: [PATCH 06/21] chore: fix typo --- src/simple-faker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 189da204e03..204312c0ca0 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -98,7 +98,7 @@ export class SimpleFaker { /** * The Randomizer to use. * - * @default generateMersenneRandomizer() + * @default generateMersenne32Randomizer() */ randomizer?: Randomizer; } = {} From dad23bcfc90c4d1f7ec8c02a6e711909f58402f4 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Thu, 21 Sep 2023 15:02:13 +0200 Subject: [PATCH 07/21] chore: fix typo --- src/faker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index 998931fd01e..f5420f76723 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -150,7 +150,7 @@ export class Faker extends SimpleFaker { /** * The Randomizer to use. * - * @default generateMersenneRandomizer() + * @default generateMersenne32Randomizer() */ randomizer?: Randomizer; }); @@ -217,7 +217,7 @@ export class Faker extends SimpleFaker { /** * The Randomizer to use. * - * @default generateMersenneRandomizer() + * @default generateMersenne32Randomizer() */ randomizer?: Randomizer; } From 41e80d4e1b65e73cb097b56a36ca673ca5d38eb0 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Thu, 21 Sep 2023 15:14:42 +0200 Subject: [PATCH 08/21] chore: improve example --- src/randomizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 36b65c47347..9258ef10347 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -57,7 +57,7 @@ export interface Randomizer { * randomizer.seed(Date.now() ^ (Math.random() * 0x100000000)); * // Fixed seeds (for reproducibility) * randomizer.seed(42); - * randomizer.seed([42, 1337]); + * randomizer.seed([42, 13.37]); */ seed(seed: number | number[]): void; } From 34729c864265aefe7db41ebe754c47d4d33a219d Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 11:23:47 +0200 Subject: [PATCH 09/21] chore: fix typo --- src/randomizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 9258ef10347..8974e155831 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -27,7 +27,7 @@ * seed: (seed) => { * const value = typeof seed === 'number' ? seed : seed[0]; * const cleaned = value.toString().replace(/[^\d]+/g, ''); - * self.step = +('1.' + clean) / 2 + 0.26183095653171535; + * self.step = +('1.' + cleaned) / 2 + 0.26183095653171535; * self.state = 0.8683615301373573 * self.step + 0.24476001502354827; * }, * } as Randomizer & { state: number; step: number }; From eea731844ec745d7d8fe8398ed0470c32100c948 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 11:31:47 +0200 Subject: [PATCH 10/21] docs: improve jsdoc usage hints --- src/faker.ts | 14 ++++++++++++-- src/randomizer.ts | 9 ++++++--- src/simple-faker.ts | 7 ++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index f5420f76723..e6b5889af36 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -124,7 +124,10 @@ export class Faker extends SimpleFaker { * * @param options The options to use. * @param options.locale The locale data to use. - * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. + * @param options.randomizer 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. + * Defaults to faker's Mersenne Twister based pseudo random number generator. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -149,6 +152,8 @@ export class Faker extends SimpleFaker { /** * 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 generateMersenne32Randomizer() */ @@ -189,7 +194,10 @@ export class Faker extends SimpleFaker { * @param options.locale The locale data to use or the name of the main locale. * @param options.locales The locale data to use. * @param options.localeFallback The name of the fallback locale to use. - * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. + * @param options.randomizer 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. + * Defaults to faker's Mersenne Twister based pseudo random number generator. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -216,6 +224,8 @@ export class Faker extends SimpleFaker { /** * 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 generateMersenne32Randomizer() */ diff --git a/src/randomizer.ts b/src/randomizer.ts index 8974e155831..65a880e09b3 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -1,8 +1,11 @@ /** - * Interface for a pseudo-random number generator. - * This interface can be used to implement a custom randomizer using third party libraries. + * Interface for a random number generator. * - *

Created instances are expected to be seeded on creation.

+ * **Note:** Normally there is no need to implement this interface directly, + * unless you want to achieve a specific goal with it. + * + * This interface enables you to use random generators from third party libraries. + * Created instances are expected to be seeded on creation. * * @example * import { FakerError, Randomizer } from '@faker-js/faker'; diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 204312c0ca0..49fc3f1c26a 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -91,12 +91,17 @@ export class SimpleFaker { * In nearly any case you should use the prebuilt `simpleFaker` instances instead of the constructor. * * @param options The options to use. - * @param options.randomizer The Randomizer to use. Defaults to faker's Mersenne Twister based pseudo random number generator. + * @param options.randomizer 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. + * Defaults to faker's Mersenne Twister based pseudo random number generator. */ 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 generateMersenne32Randomizer() */ From 4487925c230b842089e6640c52db32add5745bea Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 11:37:27 +0200 Subject: [PATCH 11/21] chore: move Randomizer to the end --- docs/.vitepress/api-pages.ts | 2 +- scripts/apidoc/generate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.vitepress/api-pages.ts b/docs/.vitepress/api-pages.ts index 57966ef453e..dad86954967 100644 --- a/docs/.vitepress/api-pages.ts +++ b/docs/.vitepress/api-pages.ts @@ -4,7 +4,6 @@ export const apiPages = [ { text: 'Overview', link: '/api/' }, { text: 'Faker', link: '/api/faker.html' }, { text: 'SimpleFaker', link: '/api/simpleFaker.html' }, - { text: 'Randomizer', link: '/api/randomizer.html' }, { text: 'Airline', link: '/api/airline.html' }, { text: 'Animal', link: '/api/animal.html' }, { text: 'Color', link: '/api/color.html' }, @@ -31,5 +30,6 @@ export const apiPages = [ { text: 'System', link: '/api/system.html' }, { text: 'Vehicle', link: '/api/vehicle.html' }, { text: 'Word', link: '/api/word.html' }, + { text: 'Randomizer', link: '/api/randomizer.html' }, { text: 'Utilities', link: '/api/utils.html' }, ]; diff --git a/scripts/apidoc/generate.ts b/scripts/apidoc/generate.ts index 2d943541892..f047cc496ba 100644 --- a/scripts/apidoc/generate.ts +++ b/scripts/apidoc/generate.ts @@ -24,10 +24,10 @@ export async function generate(): Promise { const pages = await Promise.all([ ...(await processFakerClasses(project)), - await processFakerRandomizer(project), ...(await processModules(project)).sort((a, b) => a.text.localeCompare(b.text) ), + await processFakerRandomizer(project), processFakerUtilities(project), ]); await writeApiPagesIndex(pages.map(({ text, link }) => ({ text, link }))); From c6cbcea360feeeae6c6226c9635377a8d960c5a5 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 14:41:36 +0200 Subject: [PATCH 12/21] chore: simplify --- scripts/apidoc/fakerClass.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/apidoc/fakerClass.ts b/scripts/apidoc/fakerClass.ts index 3c47e89c73c..ea190cc7e7c 100644 --- a/scripts/apidoc/fakerClass.ts +++ b/scripts/apidoc/fakerClass.ts @@ -26,7 +26,7 @@ export async function processFakerRandomizer( ): Promise { const randomizerClass = project .getChildrenByKind(ReflectionKind.Interface) - .filter((clazz) => clazz.name === 'Randomizer')[0]; + .find((clazz) => clazz.name === 'Randomizer'); return processClass(randomizerClass); } From 2374c9004921efff383050ba66e571872c194302 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 14:49:42 +0200 Subject: [PATCH 13/21] chore: use pure-rand example --- src/randomizer.ts | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 65a880e09b3..39f55085946 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -4,36 +4,23 @@ * **Note:** Normally there is no need to implement this interface directly, * unless you want to achieve a specific goal with it. * - * This interface enables you to use random generators from third party libraries. + * This interface enables you to use random generators from third party libraries such as [pure-rand](https://github.com/dubzzz/pure-rand). * Created instances are expected to be seeded on creation. * * @example - * import { FakerError, Randomizer } from '@faker-js/faker'; + * import { Randomizer } from '@faker-js/faker'; + * import { RandomGenerator, mersenne } from 'pure-rand'; * - * function generateMathRandomRandomizer(): Randomizer { - * return { - * next: () => Math.random(), // values aren't reproducible - * seed: () => { - * throw new FakerError('Math.random() Randomizer cannot be seeded'); - * }, - * }; - * } - * - * function generateSimpleRandomizer( - * seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000) + * function generatePureRandRandomizer( + * seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000), + * factory: (seed: number) => RandomGenerator = mersenne * ): Randomizer { * const self = { - * next: () => { - * self.state += self.step; - * return (self.state %= 1); - * }, - * seed: (seed) => { - * const value = typeof seed === 'number' ? seed : seed[0]; - * const cleaned = value.toString().replace(/[^\d]+/g, ''); - * self.step = +('1.' + cleaned) / 2 + 0.26183095653171535; - * self.state = 0.8683615301373573 * self.step + 0.24476001502354827; + * next: () => (self.generator.unsafeNext() >>> 0) / 0x100000000, + * seed: (seed: number | number[]) => { + * self.generator = factory(typeof seed === 'number' ? seed : seed[0]); * }, - * } as Randomizer & { state: number; step: number }; + * } as Randomizer & { generator: RandomGenerator }; * self.seed(seed); * return self; * } From db446d32082e6c149ed9956dc612037384cb2bef Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 16:29:15 +0200 Subject: [PATCH 14/21] docs: create SimpleFaker in example --- src/randomizer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/randomizer.ts b/src/randomizer.ts index 39f55085946..b2f6c9627e6 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -24,6 +24,8 @@ * self.seed(seed); * return self; * } + * + * const simpleFaker = new SimpleFaker({ randomizer: generatePureRandRandomizer() }); */ export interface Randomizer { /** From e40856dfc23c971f6e676ab593fb806f73da86f8 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 16:30:00 +0200 Subject: [PATCH 15/21] docs: create SimpleFaker in example --- src/randomizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index b2f6c9627e6..7ba5a79f2b9 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -8,7 +8,7 @@ * Created instances are expected to be seeded on creation. * * @example - * import { Randomizer } from '@faker-js/faker'; + * import { Randomizer, SimpleFaker } from '@faker-js/faker'; * import { RandomGenerator, mersenne } from 'pure-rand'; * * function generatePureRandRandomizer( From e3dd059fb4eaee7cb1113543d2cdd562330a9429 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 22:52:41 +0200 Subject: [PATCH 16/21] chore: don't use mersenne --- src/randomizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 7ba5a79f2b9..c4d2ef536b1 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -9,11 +9,11 @@ * * @example * import { Randomizer, SimpleFaker } from '@faker-js/faker'; - * import { RandomGenerator, mersenne } from 'pure-rand'; + * import { RandomGenerator, xoroshiro128plus } from 'pure-rand'; * * function generatePureRandRandomizer( * seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000), - * factory: (seed: number) => RandomGenerator = mersenne + * factory: (seed: number) => RandomGenerator = xoroshiro128plus * ): Randomizer { * const self = { * next: () => (self.generator.unsafeNext() >>> 0) / 0x100000000, From a072b6e0f3d4f33e0eca48204b35364117bce01a Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 22 Sep 2023 23:37:44 +0200 Subject: [PATCH 17/21] Update src/randomizer.ts --- src/randomizer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index c4d2ef536b1..718e8821107 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -5,7 +5,9 @@ * unless you want to achieve a specific goal with it. * * This interface enables you to use random generators from third party libraries such as [pure-rand](https://github.com/dubzzz/pure-rand). - * Created instances are expected to be seeded on creation. + * + * Instances are expected to be ready for use before being passed to any Faker constructor, + * this includes being `seed()`ed with either a random or fixed value. * * @example * import { Randomizer, SimpleFaker } from '@faker-js/faker'; From cb3691f733e0db48e1f14a30cb453312d167c244 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 24 Sep 2023 16:08:51 +0200 Subject: [PATCH 18/21] docs: simplify --- src/randomizer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 718e8821107..cff2df4bba1 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -31,8 +31,7 @@ */ export interface Randomizer { /** - * Generates a random float between `[0, 1)`. - * This method is called `next` so that it could be used as an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol). + * Generates a random float between 0 (inclusive) and 1 (exclusive). * * @example * randomizer.next() // 0.3404027920160495 From 6bf7d56d6dfaa9a5587c149280c6c11bb3f3ed51 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 2 Oct 2023 23:11:17 +0200 Subject: [PATCH 19/21] docs: improve documentation --- docs/guide/randomizer.md | 116 +++++++++++++++++++++++++++++++++++++++ src/randomizer.ts | 13 ++++- 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 docs/guide/randomizer.md diff --git a/docs/guide/randomizer.md b/docs/guide/randomizer.md new file mode 100644 index 00000000000..e03152a74ad --- /dev/null +++ b/docs/guide/randomizer.md @@ -0,0 +1,116 @@ +# Randomizer + +The [`Randomizer`](/api/randomizer) interface allows you to use a custom randomness source within Faker. + +::: warning Important +Faker's default `Randomizer` is sufficient in most cases. +Change this only if you want to use it to achieve a specific goal, such as sharing the same random generator with other instances/tools. +::: + +There are two connected use cases we have considered where this might be needed: + +1. Re-Use of the same `Randomizer` within multiple `Faker` instances. +2. The use of a random number generator from a third party library. + +## Using `Randomizer`s + +A `Randomizer` has to be set during construction of the instance: + +```ts +import { Faker, Randomizer } from '@faker-js/faker'; + +const customFaker = new Faker({ + locale: ..., + randomizer: ..., +}); +``` + +The following methods take a `Randomizer` as argument: + +- [new SimpleFaker(...)](/api/simpleFaker#constructor) +- [new Faker(...)](/api/faker#constructor) + +## Re-Using a `Randomizer` + +Sometimes it might be required to generate values in two different locales. +E.g. a Chinese person might have an English identity to simplify the communication with foreigners. +While this could also be achieved with two independent `Faker` instances like this: + +```ts +import { fakerEN, fakerZH_TW } from '@faker-js/faker'; + +fakerZH_TW.seed(5); +fakerEN.seed(5); + +const firstName = fakerZH_TW.person.firstName(); // 炫明 +const alias = fakerEN.person.firstName(); // Arthur +``` + +There might be issues regarding reproducibility, when seeding only one of them. + +By sharing a `Randomizer` between the two instances, you omit this issue by affecting all instances simultaneously. + +::: tip Note +This gets more important if the seeding happens at a different location than the data generation (e.g. due to nesting). +::: + +```ts +import { en, Faker, Randomizer, zh_TW } from '@faker-js/faker'; + +const randomizer: Randomizer = ...; + +const customFakerEN = new Faker({ + locale: en, + randomizer, +}); + +const customFakerZH_TW = new Faker({ + locale: [zh_TW, en], + randomizer, +}); + +randomizer.seed(5); +// customFakerEN.seed(5); // Redundant +// customFakerZH_TW.seed(5); // Redundant + +const firstName = fakerZH_TW.person.firstName(); // 炫明 +const alias = fakerEN.person.firstName(); // John (different from before, because it is now the second call) +``` + +This is also relevant when trying to use faker's random number generator in third party libraries. +E.g. some libraries that can generate `string`s from a `RegExp` can be customized with a custom random number generator as well, +and since they will be used in the same context it makes sense to rely on the same randomness source to ensure the values are reproducible. + +## Third-Party `Randomizer`s + +Sometimes you might want to use a custom/third-party random number generator. +This can be achieved by implementing your own `Randomizer` and passing it to [supported methods](#using-randomizers). + +::: tip Note +Faker does not ship `Randomizers` for third-party libraries and does not provide support for bridging the gap between libraries. +The following examples show how the interface can be implemented, but they are not tested for correctness. +Feel free to submit more `Randomizer` examples for other popular packages. +::: + +### Pure-Rand + +The following is an example for a [pure-rand](https://github.com/dubzzz/pure-rand) based `Randomizer`: + +```ts +import { Faker, Randomizer, SimpleFaker } from '@faker-js/faker'; +import { RandomGenerator, xoroshiro128plus } from 'pure-rand'; + +export function generatePureRandRandomizer( + seed: number | number[] = Date.now() ^ (Math.random() * 0x100000000), + factory: (seed: number) => RandomGenerator = xoroshiro128plus +): Randomizer { + const self = { + next: () => (self.generator.unsafeNext() >>> 0) / 0x100000000, + seed: (seed: number | number[]) => { + self.generator = factory(typeof seed === 'number' ? seed : seed[0]); + }, + } as Randomizer & { generator: RandomGenerator }; + self.seed(seed); + return self; +} +``` diff --git a/src/randomizer.ts b/src/randomizer.ts index cff2df4bba1..9654f0b043b 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -9,8 +9,10 @@ * Instances are expected to be ready for use before being passed to any Faker constructor, * this includes being `seed()`ed with either a random or fixed value. * + * For more information please refer to the [documentation](/api/randomizer). + * * @example - * import { Randomizer, SimpleFaker } from '@faker-js/faker'; + * import { Faker, Randomizer, SimpleFaker } from '@faker-js/faker'; * import { RandomGenerator, xoroshiro128plus } from 'pure-rand'; * * function generatePureRandRandomizer( @@ -27,7 +29,14 @@ * return self; * } * - * const simpleFaker = new SimpleFaker({ randomizer: generatePureRandRandomizer() }); + * const simpleFaker = new SimpleFaker({ + * randomizer: generatePureRandRandomizer(), + * }); + * + * const faker = new Faker({ + * locale: ..., + * randomizer: generatePureRandRandomizer(), + * }); */ export interface Randomizer { /** From 67bada3ea336e23cfe941ae0a4cdb2e6514a7ea0 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 2 Oct 2023 23:16:04 +0200 Subject: [PATCH 20/21] docs: slightly adjust examples --- src/randomizer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/randomizer.ts b/src/randomizer.ts index 9654f0b043b..3264ec1da82 100644 --- a/src/randomizer.ts +++ b/src/randomizer.ts @@ -29,13 +29,13 @@ * return self; * } * - * const simpleFaker = new SimpleFaker({ - * randomizer: generatePureRandRandomizer(), - * }); + * const randomizer = generatePureRandRandomizer(); + * + * const simpleFaker = new SimpleFaker({ randomizer }); * * const faker = new Faker({ * locale: ..., - * randomizer: generatePureRandRandomizer(), + * randomizer, * }); */ export interface Randomizer { From f98c2ee6d3f89f126a247f103f94922db6bdd017 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Mon, 2 Oct 2023 23:19:13 +0200 Subject: [PATCH 21/21] chore: add nav entry --- docs/.vitepress/config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index f43896de465..12fea107d34 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -200,6 +200,10 @@ const config = defineConfig({ text: 'Frameworks', link: '/guide/frameworks', }, + { + text: 'Randomizer', + link: '/guide/randomizer', + }, { text: 'Upgrading to v8', link: '/guide/upgrading',