Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
30e56f9
feat: introduce fakerRegistry
ST-DDT Apr 19, 2026
e6a75d2
docs: prepare for SMFs
ST-DDT Apr 19, 2026
0656236
refactor: transform basics
ST-DDT Apr 9, 2026
65e54b5
test: enable SMF refresh test
ST-DDT Apr 19, 2026
d1b7121
Merge branch 'next' into feat/samfn/utils
ST-DDT Apr 22, 2026
4a50173
chore: replace SMF term
ST-DDT Apr 23, 2026
c06e403
chore: explicit registry typing
ST-DDT Apr 23, 2026
df682b8
docs: fix apidocs refresh
ST-DDT Apr 23, 2026
48f88f6
Update src/registry.types.ts
ST-DDT Apr 23, 2026
b844881
chore: edit after review
ST-DDT Apr 24, 2026
5100ade
Merge branch 'next' into feat/samfn/utils
ST-DDT Apr 24, 2026
36d13af
chore: hide default value
ST-DDT Apr 25, 2026
dd4090e
chore: use code groups for examples
ST-DDT Apr 25, 2026
f25f8d7
chore: simplify code
ST-DDT Apr 25, 2026
a739b8f
chore: fix examples
ST-DDT Apr 25, 2026
8bc25a8
Update test/scripts/apidocs/verify-jsdoc-tags.spec.ts
ST-DDT Apr 25, 2026
aae7f8b
Update test/scripts/apidocs/verify-jsdoc-tags.spec.ts
ST-DDT Apr 25, 2026
87eab2a
Merge branch 'next' into feat/samfn/utils
ST-DDT Apr 26, 2026
f9b1812
Merge branch 'next' into feat/samfn/utils
ST-DDT May 1, 2026
4dfd622
Merge branch 'next' into feat/samfn/utils
ST-DDT May 3, 2026
14f8028
Merge branch 'next' into feat/samfn/utils
ST-DDT May 6, 2026
b8a1cd0
chore: rename for consistency
ST-DDT May 8, 2026
b328002
Merge branch 'next' into feat/samfn/utils
ST-DDT May 10, 2026
60eb425
refactor: remove registry
ST-DDT May 10, 2026
1079527
chore: cleanup
ST-DDT May 10, 2026
563e71f
chore: update snapshots
ST-DDT May 10, 2026
11ffcf4
chore: extend tests
ST-DDT May 10, 2026
e25a39d
chore: cleanup
ST-DDT May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/.vitepress/components/api-docs/refreshable-code.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function initRefresh(): Element[] {
// Keep in sync with ref scripts/shared/refreshable-code.ts
if (
domLines[lineIndex]?.children.length === 0 ||
!/^\w*faker\w*\.|^distributor\(/i.test(
!/^\w*faker\w*\.|^\w+\(fakerCore|^distributor\(/i.test(
domLines[lineIndex]?.textContent ?? ''
)
) {
Expand Down
33 changes: 26 additions & 7 deletions scripts/apidocs/output/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,27 @@ editLink: false
* @param pages The pages to write.
*/
export async function writePages(pages: RawApiDocsPage[]): Promise<void> {
await Promise.all(pages.map(writePage));
const registryHints: Record<string, string> = Object.fromEntries(
pages.flatMap((page) =>
page.methods.map((method) => [method.name, page.camelTitle])
)
);
Comment thread
xDivisionByZerox marked this conversation as resolved.
await Promise.all(pages.map((page) => writePage(page, registryHints)));
}

/**
* Writes the api docs page and data for the given module to the correct location.
*
* @param page The page to write.
* @param registryHints Hints for accessing standalone functions via module registry.
*/
async function writePage(page: RawApiDocsPage): Promise<void> {
async function writePage(
page: RawApiDocsPage,
registryHints: Record<string, string> = {}
): Promise<void> {
try {
await writePageMarkdown(page);
await writePageData(page);
await writePageData(page, registryHints);
} catch (error) {
throw new Error(`Error writing page ${page.title}`, { cause: error });
}
Expand Down Expand Up @@ -103,20 +112,29 @@ async function writePageMarkdown(page: RawApiDocsPage): Promise<void> {
* Writes the api docs data for the given module to correct location.
*
* @param page The page to write.
* @param registryHints Hints for accessing standalone functions via module registry.
*/
async function writePageData(page: RawApiDocsPage): Promise<void> {
async function writePageData(
page: RawApiDocsPage,
registryHints: Record<string, string> = {}
): Promise<void> {
const { camelTitle, methods } = page;
const pageData: Record<string, ApiDocsMethod> = Object.fromEntries(
await Promise.all(
methods.map(async (method) => [method.name, await toMethodData(method)])
)
);
const prioritizedRegistryHints = {
...registryHints,
// own module > other modules
...Object.fromEntries(methods.map((method) => [method.name, camelTitle])),
};

const refreshFunctions: Record<string, string> = Object.fromEntries(
await Promise.all(
methods.map(async (method) => [
method.name,
await toRefreshFunction(method),
await toRefreshFunction(method, prioritizedRegistryHints),
])
)
);
Expand Down Expand Up @@ -218,12 +236,13 @@ export function extractSummaryDefault(description: string): string | undefined {
}

export async function toRefreshFunction(
method: RawApiDocsMethod
method: RawApiDocsMethod,
registryHints: Record<string, string> = {}
): Promise<string> {
const { name, signatures } = method;
const signatureData = required(signatures.at(-1), 'method signature');
const { examples } = signatureData;

const exampleCode = examples.join('\n');
return await toRefreshableCode(name, exampleCode);
return await toRefreshableCode(name, exampleCode, registryHints);
}
33 changes: 29 additions & 4 deletions scripts/shared/refreshable-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,32 @@ import { formatTypescript } from '../shared/format';

export async function toRefreshableCode(
name: string,
exampleCode: string
exampleCode: string,
moduleHints: Record<string, string> = {}
): Promise<string> {
const exampleLines = exampleCode
.replaceAll(/ ?\/\/.*$/gm, '') // Remove comments
.replaceAll(/^import .*$/gm, '') // Remove imports
.replaceAll(
// record results of relevant calls
// Keep in sync with docs/.vitepress/components/api-docs/refreshable-code.vue
/^(\w*faker\w*\..+(?:(?:.|\n..)*\n[^ ])?\)(?:\.\w+)?|distributor\(.+\));?$/gim,
`try { result.push($1); } catch (error: unknown) { result.push(error instanceof Error ? error.name : 'Error'); }\n`
/\b(?<!\.)(\w+)\((faker\.)?fakerCore/g,
(match, p1) => {
// Access standalone functions via module registry if possible
// firstName(fakerCore) -> fakerRegistry.person.firstName(fakerCore)
if (moduleHints[p1]) {
return `fakerRegistry.${moduleHints[p1]}.${match}`;
}

throw new Error(
`Unable to find module hint for ${p1} in example code for ${name}`
);
}
)
.replaceAll(
// Record results of relevant calls
// Keep in sync with docs/.vitepress/components/api-docs/refreshable-code.vue
/^((?<callBase>\w*faker\w*\.|distributor\()(?<consumeToEOL>.+)(?<multiline>(?<consumeIndented>\n +.*)+(?<finalLine>\n[^ \n]+))?\)(?<nestedProperty>\.\w+)?);?$/gim,
`try { result.push($1); } catch (error: unknown) { result.push(error instanceof Error ? error.name : 'Error'); console.log('Error in example for ${name}:', error); }\n`
);

if (!exampleLines.includes('try { result.push(')) {
Expand All @@ -22,9 +38,18 @@ export async function toRefreshableCode(
const fullMethod = `async (): Promise<unknown[]> => {
await enableFaker();
const result: unknown[] = [];
${/(?<!\.)fakerCore/.test(exampleCode) ? 'const fakerCore = faker.fakerCore;' : ''}
${
// TODO @ST-DDT 2026-04-23: Remove when past is transformed into standalone function
exampleCode.includes('past(fakerCore')
? 'fakerRegistry.date = { past: (fakerCore) => new SimpleFaker(fakerCore).date.past() };'
: ''
}

${exampleLines}

${exampleCode.includes('setDefaultRefDate(') ? 'faker.setDefaultRefDate(); // Reset' : ''}

return result;
}`;
try {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export type { SystemModule } from './modules/system';
export type { VehicleModule } from './modules/vehicle';
export type { WordModule } from './modules/word';
export type { Randomizer } from './randomizer';
export { fakerRegistry } from './registry';
export { SimpleFaker, simpleFaker } from './simple-faker';
export { mergeLocales } from './utils/merge-locales';
Comment thread
ST-DDT marked this conversation as resolved.
export {
Expand Down
16 changes: 16 additions & 0 deletions src/registry.ts
Comment thread
xDivisionByZerox marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { FakerRegistry } from './registry.types';
import { utilsModule as utils } from './utils/registry';

// TODO @ST-DDT 2026-04-12: This file will be auto generated in a future PR.

/**
* Global registry for the Faker library, containing all module registries with their standalone module functions.
*
* You normally don't need to access this registry unless you want to call `fake()` or other custom code needing to lookup standalone functions.
*
* @example
* fake(fakerCore, 'The date is {{utils.getDefaultRefDate}}', [ fakerRegistry, fakerCore.locale.raw ]);
*/
export const fakerRegistry = {
utils,
} as const satisfies FakerRegistry;
11 changes: 11 additions & 0 deletions src/registry.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { FakerCore } from './core';

/**
* Global Registry for the Faker library, containing all module registries.
*/
export type FakerRegistry = Record<string, ModuleRegistry>;

/**
* Per module registry containing the module's standalone functions.
*/
export type ModuleRegistry = Record<string, (fakerCore: FakerCore) => unknown>;
Comment thread
ST-DDT marked this conversation as resolved.
Outdated
12 changes: 4 additions & 8 deletions src/simple-faker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { SimpleHelpersModule } from './modules/helpers';
import { SimpleLocationModule } from './modules/location';
import { NumberModule } from './modules/number';
import { StringModule } from './modules/string';

export const DEFAULT_REF_DATE_SOURCE: () => Date = () => new Date();
import { getDefaultRefDate } from './utils/get-default-ref-date';
import { setDefaultRefDate as utilsSetDefaultRefDate } from './utils/set-default-ref-date';
Comment thread
ST-DDT marked this conversation as resolved.
Outdated

/**
* This is a simplified Faker class that doesn't need any localized data to generate its output.
Expand Down Expand Up @@ -42,7 +42,7 @@ export class SimpleFaker {
* Gets a new reference date used to generate relative dates.
*/
get defaultRefDate(): () => Date {
return this.fakerCore.config.defaultRefDate ?? DEFAULT_REF_DATE_SOURCE;
return () => getDefaultRefDate(this.fakerCore);
}

/**
Expand Down Expand Up @@ -81,11 +81,7 @@ export class SimpleFaker {
setDefaultRefDate(
dateOrSource: string | Date | number | (() => Date) = () => new Date()
): void {
if (typeof dateOrSource === 'function') {
this.fakerCore.config.defaultRefDate = dateOrSource;
} else {
this.fakerCore.config.defaultRefDate = () => new Date(dateOrSource);
}
utilsSetDefaultRefDate(this.fakerCore, dateOrSource);
}

readonly datatype: DatatypeModule = new DatatypeModule(this);
Expand Down
39 changes: 39 additions & 0 deletions src/utils/get-default-ref-date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { FakerCore } from '../core';

export const DEFAULT_REF_DATE_SOURCE: () => Date = () => new Date();
Comment thread
ST-DDT marked this conversation as resolved.
Outdated

/**
* Gets a new reference date used to generate relative dates.
*
* If `fakerCore.config.defaultRefDate` is defined, it will be used to get the default reference date. Otherwise, the current date will be used.
*
* @param fakerCore The FakerCore instance to get it from.
*
* @returns The newly created default reference date.
*
* @example
* fakerCore.randomizer.seed(1234);
*
* // Default behavior
* // setDefaultRefDate(fakerCore);
* past(fakerCore); // Changes based on the current date/time
*
* // Use a static ref date
* setDefaultRefDate(fakerCore, new Date('2020-01-01'));
* past(fakerCore); // Reproducible '2019-07-03T08:27:58.118Z'
Comment thread
ST-DDT marked this conversation as resolved.
Outdated
*
* // Use a ref date that changes every time it is used
* let clock = new Date("2020-01-01").getTime();
* setDefaultRefDate(fakerCore, () => {
* clock += 1000; // +1s
* return new Date(clock);
* });
*
* getDefaultRefDate(fakerCore) // 2020-01-01T00:00:01Z
* getDefaultRefDate(fakerCore) // 2020-01-01T00:00:02Z
Comment thread
ST-DDT marked this conversation as resolved.
*
* @since 10.5.0
*/
export function getDefaultRefDate(fakerCore: FakerCore): Date {
return fakerCore.config.defaultRefDate?.() ?? DEFAULT_REF_DATE_SOURCE();
}
Comment thread
xDivisionByZerox marked this conversation as resolved.
11 changes: 11 additions & 0 deletions src/utils/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ModuleRegistry } from '../registry.types';
import { getDefaultRefDate } from './get-default-ref-date';
import { setDefaultRefDate } from './set-default-ref-date';

/**
* Registry module containing all standalone utility functions for the Faker library.
*/
export const utilsModule = {
getDefaultRefDate,
setDefaultRefDate,
} as const satisfies ModuleRegistry;
46 changes: 46 additions & 0 deletions src/utils/set-default-ref-date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { FakerCore } from '../core';

/**
* Sets the `refDate` source to use if no `refDate` date is passed to the date methods.
*
* @param fakerCore The FakerCore instance to use.
* @param dateOrSource The function or the static value used to generate the `refDate` date instance.
* The function must return a new valid `Date` instance for every call.
* Defaults to `() => new Date()`.
*
* @see [Reproducible Results](https://fakerjs.dev/guide/usage.html#reproducible-results)
* @see faker.seed(): For generating reproducible values.
*
* @example
* fakerCore.randomizer.seed(1234);
*
* // Default behavior
* // setDefaultRefDate(fakerCore);
* past(fakerCore); // Changes based on the current date/time
*
* // Use a static ref date
* setDefaultRefDate(fakerCore, new Date('2020-01-01'));
* past(fakerCore); // Reproducible '2019-07-03T08:27:58.118Z'
Comment thread
ST-DDT marked this conversation as resolved.
*
* // Use a ref date that changes every time it is used
* let clock = new Date("2020-01-01").getTime();
* setDefaultRefDate(fakerCore, () => {
* clock += 1000; // +1s
* return new Date(clock);
* });
*
* getDefaultRefDate(fakerCore) // 2020-01-01T00:00:01Z
* getDefaultRefDate(fakerCore) // 2020-01-01T00:00:02Z
*
* @since 10.5.0
*/
export function setDefaultRefDate(
fakerCore: FakerCore,
dateOrSource: string | Date | number | (() => Date) = () => new Date()
): void {
if (typeof dateOrSource === 'function') {
fakerCore.config.defaultRefDate = dateOrSource;
} else {
fakerCore.config.defaultRefDate = () => new Date(dateOrSource);
}
}
23 changes: 23 additions & 0 deletions test/scripts/apidocs/__snapshots__/page.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports[`toRefreshFunction > should handle multiline calls 1`] = `
);
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
Expand All @@ -29,12 +30,14 @@ exports[`toRefreshFunction > should handle multiple calls 1`] = `
result.push(faker.number.int());
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

try {
result.push(faker.number.int());
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
Expand All @@ -50,6 +53,7 @@ exports[`toRefreshFunction > should handle properties after calls 1`] = `
result.push(faker.airline.airport().name);
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
Expand All @@ -65,6 +69,7 @@ exports[`toRefreshFunction > should handle single line calls with semicolon 1`]
result.push(faker.number.int());
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
Expand All @@ -80,6 +85,24 @@ exports[`toRefreshFunction > should handle single line calls without semicolon 1
result.push(faker.number.int());
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
}"
`;

exports[`toRefreshFunction > should handle standalone functions calls 1`] = `
"async (): Promise<unknown[]> => {
await enableFaker();
const result: unknown[] = [];
const fakerCore = faker.fakerCore;

try {
result.push(fakerRegistry.utils.getDefaultRefDate(fakerCore));
} catch (error: unknown) {
result.push(error instanceof Error ? error.name : 'Error');
console.log('Error in example for test:', error);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ exports[`check docs completeness > all modules and methods are present 1`] = `
[
"generateMersenne32Randomizer",
"generateMersenne53Randomizer",
"getDefaultRefDate",
"mergeLocales",
"setDefaultRefDate",
],
],
[
Expand Down
Loading
Loading