diff --git a/src/faker8/bind.ts b/src/faker8/bind.ts new file mode 100644 index 00000000000..4fd2a84cae7 --- /dev/null +++ b/src/faker8/bind.ts @@ -0,0 +1,102 @@ +import type { FakerCore, LocalizedFakerCore } from './fakerCore'; + +type FakerFunction = (...args: any[]) => unknown; +type FakerModule = Record; +type FakerModules = Record; + +type FakerFn = ( + core: C, + ...args: P +) => R; + +type FakerFnFor = FakerFn< + C, + Parameters, + ReturnType +>; + +type FakerFnModule = { + [K in keyof M]: FakerFnFor; +}; + +type FakerFnModules = { + [K in keyof M]: FakerFnModule; +}; + +/** + * Binds the given function to the given faker core. + * + * @param fakerCore The faker core to bind the function to. + * @param fn The function to bind. + * + * @example + * const customFunction = bind(fakerCore, arrayElementFn); + * + * @since 8.0.0 + */ +export function bind( + fakerCore: C, + fn: FakerFn +): (...args: P) => R { + return (...args: P) => fn(fakerCore.fork() as C, ...args); +} + +/** + * Binds the given module to the given faker core. + * + * @param fakerCore The faker core to bind the module to. + * @param module The module to bind. + * + * @example + * const customModule = bindModule(fakerCore, datatypeFns); + * + * @since 8.0.0 + */ +export function bindModule( + fakerCore: FakerCore, + module: FakerFnModule +): M; +export function bindModule( + fakerCore: LocalizedFakerCore, + module: FakerFnModule +): M; +export function bindModule( + fakerCore: C, + module: FakerFnModule +): M { + return Object.fromEntries( + Object.entries(module).map(([key, value]) => { + return [key, bind(fakerCore, value)]; + }) + ) as M; +} + +/** + * Binds the given modules to the given faker core. + * + * @param fakerCore The faker core to bind the modules to. + * @param modules The modules to bind. + * + * @example + * const customModules = bindModules<{ datatype: Datatype }>(fakerCore, { datatype: datatypeFns }); + * + * @since 8.0.0 + */ +export function bindModules( + fakerCore: FakerCore, + modules: FakerFnModules +): Modules; +export function bindModules( + fakerCore: LocalizedFakerCore, + modules: FakerFnModules +): Modules; +export function bindModules< + Modules extends FakerModules, + Core extends FakerCore +>(fakerCore: Core, modules: FakerFnModules): Modules { + return Object.fromEntries( + Object.entries(modules).map(([key, value]) => { + return [key, bindModule(fakerCore, value)]; + }) + ) as Modules; +} diff --git a/src/faker8/datatype/index.ts b/src/faker8/datatype/index.ts new file mode 100644 index 00000000000..c955b558522 --- /dev/null +++ b/src/faker8/datatype/index.ts @@ -0,0 +1,53 @@ +// This file is generated by `pnpm run generate:some-script` + +import { bindModule } from '../bind'; +import type { FakerCore } from '../fakerCore'; +import { fakerCore } from '../fakerCore'; +import { fakerNumber, numberFn } from './number'; + +/** + * The functions of the datatype module. + */ +export const datatypeFns = { + number: numberFn, +}; + +/** + * The datatype module. + */ +export type Datatype = { + /** + * Returns a single random number in the given range. + * The bounds are inclusive. + * + * @param options Maximum value or options object. + * @param options.min Lower bound for generated number. Defaults to `0`. + * @param options.max Upper bound for generated number. Defaults to `min + 99999`. + * + * @throws When options define `max < min`. + * + * @example + * faker.datatype.number() // 55422 + * faker.datatype.number(100) // 52 + * faker.datatype.number({ min: 1000000 }) // 1031433 + * faker.datatype.number({ max: 100 }) // 42 + * faker.datatype.number({ min: 10, max: 100}) // 36 + * + * @since 8.0.0 + */ + number: typeof fakerNumber; +}; + +/** + * Creates a new datatype module that is bound to the given faker core. + * + * @param fakerCore The faker core to bind the module to. + */ +export function bindDatatypeModule(fakerCore: FakerCore): Datatype { + return bindModule(fakerCore, datatypeFns); +} + +// Either +export const fakerDatatype: Datatype = bindDatatypeModule(fakerCore); +// Or +export const fakerDatatype2: Datatype = { number: fakerNumber }; diff --git a/src/faker8/datatype/number.ts b/src/faker8/datatype/number.ts new file mode 100644 index 00000000000..3d97adb8c6c --- /dev/null +++ b/src/faker8/datatype/number.ts @@ -0,0 +1,54 @@ +import { bind } from '../bind'; +import type { FakerCore } from '../fakerCore'; +import { fakerCore } from '../fakerCore'; + +/** + * Returns a single random number in the given range. + * The bounds are inclusive. + * + * @param fakerCore The faker core to use. + * @param options Maximum value or options object. + * @param options.min Lower bound for generated number. Defaults to `0`. + * @param options.max Upper bound for generated number. Defaults to `min + 99999`. + * + * @throws When options define `max < min`. + * + * @example + * numberFn(fakerCore) // 55422 + * numberFn(fakerCore, { min: 1000000 }) // 1031433 + * numberFn(fakerCore, { max: 100 }) // 42 + * numberFn(fakerCore, { min: 10, max: 100}) // 36 + * + * @since 8.0.0 + */ +export function numberFn( + fakerCore: FakerCore, + options: { min?: number; max?: number } = {} +): number { + const { min = 0, max = min + 99999 } = options; + return fakerCore.mersenne.next({ min, max }); +} + +// The following part is generated by `pnpm run generate:some-script` + +/** + * Returns a single random number in the given range. + * The bounds are inclusive. + * + * @param options Maximum value or options object. + * @param options.min Lower bound for generated number. Defaults to `0`. + * @param options.max Upper bound for generated number. Defaults to `min + 99999`. + * + * @throws When options define `max < min`. + * + * @example + * fakerNumber() // 55422 + * fakerNumber(100) // 52 + * fakerNumber({ min: 1000000 }) // 1031433 + * fakerNumber({ max: 100 }) // 42 + * fakerNumber({ min: 10, max: 100}) // 36 + * + * @since 8.0.0 + */ +export const fakerNumber: (options?: { min?: number; max?: number }) => number = + bind(fakerCore, numberFn); diff --git a/src/faker8/faker.ts b/src/faker8/faker.ts new file mode 100644 index 00000000000..5ddaeb44d1c --- /dev/null +++ b/src/faker8/faker.ts @@ -0,0 +1,35 @@ +import { bindModules } from './bind'; +import type { Datatype } from './datatype'; +import { datatypeFns } from './datatype'; +import type { LocalizedFakerCore } from './fakerCore'; +import type { Forkable } from './forkable'; +import { forkable } from './forkable'; +import type { Helpers } from './helpers'; +import { helpersFns } from './helpers'; +import type { Person } from './person'; +import { personFns } from './person'; + +export const fnsModules = { + datatype: datatypeFns, + helpers: helpersFns, + person: personFns, +}; + +export type FakerModules = { + datatype: Datatype; + helpers: Helpers; + person: Person; +}; + +export type Faker = Forkable; + +/** + * Creates a new faker instance that is bound to the given faker core. + * + * @param fakerCore The faker core to bind the module to. + */ +export function bindFaker(fakerCore: LocalizedFakerCore): Faker { + return forkable(fakerCore, (core) => + bindModules(core, fnsModules) + ); +} diff --git a/src/faker8/fakerCore.ts b/src/faker8/fakerCore.ts new file mode 100644 index 00000000000..b6abe101373 --- /dev/null +++ b/src/faker8/fakerCore.ts @@ -0,0 +1,51 @@ +import type { LocaleDefinition } from 'src'; +import en from '../locales/en'; +import enPerson from '../locales/en/person'; +import type { Mersenne } from './mersenne'; +import { default as newMersenne } from './mersenne'; + +export interface FakerCore { + /** + * @internal + */ + readonly mersenne: Mersenne; + readonly fork: () => FakerCore; + readonly derive: () => FakerCore; +} + +export interface LocalizedFakerCore extends FakerCore { + readonly definitions: LocaleDefinition; + readonly fork: () => LocalizedFakerCore; + readonly derive: () => LocalizedFakerCore; +} + +export function createFakerCore(mersenne?: Mersenne): FakerCore { + mersenne = mersenne ?? newMersenne(); + return { + mersenne, + fork: () => createFakerCore(mersenne.fork()), + derive: () => createFakerCore(mersenne.derive()), + }; +} + +export function createLocalizedFakerCore( + definitions: Partial, + mersenne?: Mersenne +): LocalizedFakerCore { + mersenne = mersenne ?? newMersenne(); + return { + mersenne, + definitions: definitions as unknown as LocaleDefinition, + fork: () => createLocalizedFakerCore(definitions, mersenne.fork()), + derive: () => createLocalizedFakerCore(definitions, mersenne.derive()), + }; +} + +const mersenne = newMersenne(); + +export const fakerCore = createFakerCore(mersenne); +export const localizedFakerCore = createLocalizedFakerCore(en, mersenne); +export const personFakerCore = createLocalizedFakerCore( + { person: enPerson }, + mersenne +); diff --git a/src/faker8/forkable.ts b/src/faker8/forkable.ts new file mode 100644 index 00000000000..03f482ef743 --- /dev/null +++ b/src/faker8/forkable.ts @@ -0,0 +1,25 @@ +import type { FakerCore } from './fakerCore'; + +export type Forkable = T & { + fork: () => Forkable; + derive: () => Forkable; +}; + +export function forkable( + fakerCore: C, + factory: (fakerCore: C) => T +): Forkable { + const result = factory(fakerCore); + if (typeof result === 'object') { + return { + ...result, + fork: () => forkable(fakerCore.fork() as C, factory), + derive: () => forkable(fakerCore.derive() as C, factory), + }; + } else if (typeof result === 'function') { + const fn = result as unknown as Forkable; + fn.fork = () => forkable(fakerCore.fork() as C, factory); + fn.derive = () => forkable(fakerCore.derive() as C, factory); + return fn; + } +} diff --git a/src/faker8/helpers/arrayElement.ts b/src/faker8/helpers/arrayElement.ts new file mode 100644 index 00000000000..8d70ca8dbc4 --- /dev/null +++ b/src/faker8/helpers/arrayElement.ts @@ -0,0 +1,40 @@ +import { bind } from '../bind'; +import { numberFn } from '../datatype/number'; +import type { FakerCore } from '../fakerCore'; +import { fakerCore } from '../fakerCore'; + +/** + * Returns a random element from the given array. + * + * @template T The type of the entries to pick from. + * @param fakerCore The faker core to use. + * @param values The array to pick the value from. + * + * @example + * fakerArrayElement(fakerCore, ['cat', 'dog', 'mouse']) // 'dog' + * + * @since 8.0.0 + */ +export function arrayElementFn(fakerCore: FakerCore, values: T[]): T { + const { length } = values; + const index = numberFn(fakerCore, { min: 0, max: length - 1 }); + return values[index]; +} + +// The following part is generated by `pnpm run generate:some-script` + +/** + * Returns a random element from the given array. + * + * @template T The type of the entries to pick from. + * @param values The array to pick the value from. + * + * @example + * fakerArrayElement(['cat', 'dog', 'mouse']) // 'dog' + * + * @since 8.0.0 + */ +export const fakerArrayElement: (values: T[]) => T = bind( + fakerCore, + arrayElementFn +); diff --git a/src/faker8/helpers/index.ts b/src/faker8/helpers/index.ts new file mode 100644 index 00000000000..39c89ae8203 --- /dev/null +++ b/src/faker8/helpers/index.ts @@ -0,0 +1,45 @@ +// This file is generated by `pnpm run generate:some-script` + +import { bindModule } from '../bind'; +import type { FakerCore } from '../fakerCore'; +import { fakerCore } from '../fakerCore'; +import { arrayElementFn, fakerArrayElement } from './arrayElement'; + +/** + * The functions of the helpers module. + */ +export const helpersFns = { + arrayElement: arrayElementFn, +}; + +/** + * The helpers module. + */ +export type Helpers = { + /** + * Returns a random element from the given array. + * + * @template T The type of the entries to pick from. + * @param values The array to pick the value from. + * + * @example + * fakerArrayElement(['cat', 'dog', 'mouse']) // 'dog' + * + * @since 8.0.0 + */ + arrayElement: (values: T[]) => T; +}; + +/** + * Creates a new helpers module that is bound to the given faker instance. + * + * @param fakerCore The faker instance to bind the module to. + */ +export function bindHelpersModule(fakerCore: FakerCore): Helpers { + return bindModule(fakerCore, helpersFns); +} + +// Either +export const fakerHelpers: Helpers = bindHelpersModule(fakerCore); +// Or +export const fakerHelpers2: Helpers = { arrayElement: fakerArrayElement }; diff --git a/src/faker8/index.ts b/src/faker8/index.ts new file mode 100644 index 00000000000..2a284f743d8 --- /dev/null +++ b/src/faker8/index.ts @@ -0,0 +1,17 @@ +import { fakerDatatype } from './datatype'; +import type { Faker } from './faker'; +import { bindFaker } from './faker'; +import { localizedFakerCore } from './fakerCore'; +import { fakerHelpers } from './helpers'; +import { fakerPerson } from './person'; + +// Either +export const faker: Faker = bindFaker(localizedFakerCore); +// Or +export const faker2: Faker = { + datatype: fakerDatatype, + helpers: fakerHelpers, + person: fakerPerson, + fork: () => bindFaker(localizedFakerCore.fork()), + derive: () => bindFaker(localizedFakerCore.derive()), +}; diff --git a/src/faker8/mersenne.ts b/src/faker8/mersenne.ts new file mode 100644 index 00000000000..eb9d0cbf3a6 --- /dev/null +++ b/src/faker8/mersenne.ts @@ -0,0 +1,51 @@ +import MersenneTwister19937 from 'src/internal/mersenne/twister'; + +export interface Mersenne { + /** + * Generates a random number between `[min, max)`. The result is already floored. + * + * @param options The options to generate a random number. + * @param options.min The minimum number. + * @param options.max The maximum number. + */ + next(options: { max: number; min: number }): number; + + /** + * Sets the seed to use. + * + * @param seed The seed to use. + */ + seed(seed: number | number[]): void; + + fork(): Mersenne; + + derive(): Mersenne; +} + +export default function mersenne(): Mersenne { + const twister = new MersenneTwister19937(); + + twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)); + + return { + next({ min, max }): number { + return Math.floor(twister.genrandReal2() * (max - min) + min); + }, + + seed(seed: number | number[]): void { + if (typeof seed === 'number') { + twister.initGenrand(seed); + } else if (Array.isArray(seed)) { + twister.initByArray(seed, seed.length); + } + }, + + fork(): Mersenne { + return mersenne(); // TODO + }, + + derive(): Mersenne { + return mersenne(); // TODO + }, + }; +} diff --git a/src/faker8/person/firstName.ts b/src/faker8/person/firstName.ts new file mode 100644 index 00000000000..6b2e8f529a1 --- /dev/null +++ b/src/faker8/person/firstName.ts @@ -0,0 +1,58 @@ +import type { SexType } from 'src/modules/person'; +import { bind } from '../bind'; +import type { LocalizedFakerCore } from '../fakerCore'; +import { localizedFakerCore } from '../fakerCore'; +import { arrayElementFn } from '../helpers/arrayElement'; + +/** + * Returns a random first name. + * + * @param fakerCore The faker core to use. + * @param sex The optional sex to use. + * Can be either `'female'` or `'male'`. + * + * @see fakerFirstName + * + * @example + * firstNameFn(fakerCore) // 'Antwan' + * firstNameFn(fakerCore, 'female') // 'Victoria' + * firstNameFn(fakerCore, 'male') // 'Tom' + * + * @since 8.0.0 + */ +export function firstNameFn( + fakerCore: LocalizedFakerCore, + sex?: SexType +): string { + // selectDefinitions(...) + const definitions: string[] = sex + ? sex === 'female' + ? fakerCore.definitions.person.female_first_name + : fakerCore.definitions.person.male_first_name + : fakerCore.definitions.person.first_name; + + return arrayElementFn(fakerCore, definitions); +} + +// The following part is generated by `pnpm run generate:some-script` + +/** + * Returns a random first name. + * + * @alias faker.name.firstName + * @alias fakerName.firstName + * + * @param sex The optional sex to use. + * Can be either `'female'` or `'male'`. + * + * @example + * fakerFirstName() // 'Antwan' + * fakerFirstName('female') // 'Victoria' + * fakerFirstName('male') // 'Tom' + * + * @since 8.0.0 + */ +export const fakerFirstName: (sex?: SexType) => string = bind( + localizedFakerCore, + firstNameFn +); diff --git a/src/faker8/person/index.ts b/src/faker8/person/index.ts new file mode 100644 index 00000000000..5905554e1a3 --- /dev/null +++ b/src/faker8/person/index.ts @@ -0,0 +1,50 @@ +// This file is generated by `pnpm run generate:some-script` + +import { bindModule } from '../bind'; +import type { LocalizedFakerCore } from '../fakerCore'; +import { localizedFakerCore } from '../fakerCore'; +import { fakerFirstName, firstNameFn } from './firstname'; + +/** + * The functions of the person module. + */ +export const personFns = { + firstName: firstNameFn, +}; + +/** + * The person module. + */ +export type Person = Readonly<{ + /** + * Returns a random first name. + * + * @alias faker.name.firstName + * @alias fakerName.firstName + * + * @param sex The optional sex to use. + * Can be either `'female'` or `'male'`. + * + * @example + * fakerFirstName() // 'Antwan' + * fakerFirstName('female') // 'Victoria' + * fakerFirstName('male') // 'Tom' + * + * @since 8.0.0 + */ + firstName: typeof fakerFirstName; +}>; + +/** + * Creates a new person module that is bound to the given faker core. + * + * @param fakerCore The faker core to bind the module to. + */ +export function bindPersonModule(fakerCore: LocalizedFakerCore): Person { + return bindModule(fakerCore, personFns); +} + +// Either +export const fakerPerson: Person = bindPersonModule(localizedFakerCore); +// Or +export const fakerPerson2: Person = { firstName: fakerFirstName }; diff --git a/src/faker8/usage.ts b/src/faker8/usage.ts new file mode 100644 index 00000000000..8d328bc97d1 --- /dev/null +++ b/src/faker8/usage.ts @@ -0,0 +1,36 @@ +import { faker } from '.'; +import { bind, bindModules } from './bind'; +import type { Datatype } from './datatype'; +import { bindDatatypeModule, datatypeFns } from './datatype'; +import { numberFn } from './datatype/number'; +import { fakerCore } from './fakerCore'; +import { forkable } from './forkable'; +import type { Helpers } from './helpers'; +import { helpersFns } from './helpers'; +import { fakerPerson } from './person'; +import { fakerFirstName } from './person/firstname'; + +const firstName = fakerFirstName(); +const firstName2 = fakerPerson.firstName(); +const firstName3 = faker.person.firstName(); +console.log(firstName, firstName2, firstName3); + +faker.fork().fork().person.firstName(); + +// Custom + +const fDataType = forkable(fakerCore, bindDatatypeModule); +console.log(fDataType.fork().fork().number()); + +const fNumber = forkable(fakerCore, (core) => bind(core, numberFn)); +console.log(fNumber.fork().fork()()); + +const subSet = bindModules<{ datatype: Datatype; helpers: Helpers }>( + fakerCore, + { + datatype: datatypeFns, + helpers: helpersFns, + } +); + +console.log(subSet.datatype.number());