Skip to content

@jaypie/fabricator

Prerequisites: npm install @jaypie/fabricator

Status: Experimental - APIs may change

@jaypie/fabricator provides deterministic test data generation with seeding support, built on Faker.js with Jaypie conventions.

Terminal window
npm install @jaypie/fabricator
Export Purpose
Fabricator Base class for data generation
fabricator Factory function
EventFabricator Temporal event generation
CHANCE Probability constants
Constant Value Description
UNCOMMON 0.236 ~1 in 4
RARE 0.146 ~1 in 7
EPIC 0.021 ~1 in 50
LEGENDARY 0.005 ~1 in 200
import { fabricator } from "@jaypie/fabricator";
const fab = fabricator("seed-123");
fab.person.firstName(); // Always same for seed
fab.person.lastName();
fab.internet.email();
const fab1 = fabricator("seed-123");
const fab2 = fabricator("seed-123");
fab1.person.firstName() === fab2.person.firstName(); // true
const fab = fabricator("seed");
fab.random(); // 0-1
fab.random({ min: 1, max: 100 }); // 1-100
fab.random({ integer: true }); // Whole number
fab.random({ precision: 2 }); // 0.00-1.00
fab.random({ min: 0, max: 100, precision: 2 }); // 0.00-100.00
fab.random({
min: 0,
max: 100,
distribution: "normal",
mean: 50,
stdDev: 15,
});
const price = fab.random({
min: 10,
max: 1000,
precision: 2,
});
// 123.45
import { Fabricator } from "@jaypie/fabricator";
const fab = new Fabricator("seed");
fab.generate.words(); // "swift eagle"
fab.generate.words(3); // "swift eagle dance"

Generate deterministic prose for fixtures, snapshots, or seeded benchmarks.

const fab = fabricator("seed");
fab.corpus(); // 108 words of English-ish prose (default)
fab.corpus(1000); // 1000 words
fab.corpus({ wordsPerPeriod: 10 });
fab.corpus(500, { typoRate: 0.20 });
  • Each call advances the fabricator’s faker state, so successive calls with the same parameters return different output.
  • Replaying from a fresh fabricator(seed) reproduces the same sequence.
  • Word counts and options are folded into the per-call seed, so different parameters produce independent streams — corpus(100) and corpus(101) differ across the whole text, not by one word.

Mix domain vocabulary into the output. By default the custom pool blends 50/50 with default English; typo and phonotactic invention rates stay at their defaults.

fab.corpus(500, { corpus: deployLogText });
fab.corpus(500, { words: [["deploy", 5], ["lambda", 2]] });
fab.corpus(500, { corpus: deployLogText, blend: 0.7 });
fab.corpus(500, { corpus: deployLogText, replaceDefaults: true });

For tokens that aren’t word-shaped (UUIDs, dollar amounts, IDs), pass functions. Each function receives the fabricator and emits one token per draw. The weight is the share of total content tokens taken from the main word stream.

// Single function — shorthand
fab.corpus(500, {
functions: [({ fab }) => fab.string.uuid(), 0.03],
});
// Multiple functions
fab.corpus(500, {
functions: [
[({ fab }) => fab.string.uuid(), 0.03],
[({ fab }) => "$" + fab.finance.amount(), 0.04],
[({ fab }) => fab.internet.email(), 0.02],
],
});
const person = fab.generate.person();
// {
// firstName: "Alice",
// lastName: "Smith",
// email: "alice.smith@example.com",
// phone: "+1-555-123-4567"
// }
const person = fab.generate.person({
includeMiddleName: CHANCE.UNCOMMON,
includeNickname: CHANCE.RARE,
});
import { Fabricator } from "@jaypie/fabricator";
class UserFabricator extends Fabricator {
user() {
return {
id: this.string.uuid(),
name: this.person.fullName(),
email: this.internet.email(),
createdAt: this.date.recent(),
role: this.helpers.arrayElement(["admin", "user", "guest"]),
};
}
users(count: number) {
return Array.from({ length: count }, () => this.user());
}
}
const fab = new UserFabricator("seed");
const users = fab.users(10);

Generate temporal event sequences.

import { EventFabricator } from "@jaypie/fabricator";
const events = new EventFabricator("seed", {
startDate: new Date("2024-01-01"),
endDate: new Date("2024-12-31"),
});
const loginEvents = events.generate({
type: "login",
count: 100,
distribution: "uniform",
});
const events = events.generate({
type: "purchase",
count: 50,
distribution: "weighted",
weights: {
morning: 0.3, // 6am-12pm
afternoon: 0.4, // 12pm-6pm
evening: 0.25, // 6pm-10pm
night: 0.05, // 10pm-6am
},
});

Events that follow other events:

const purchases = events.generate({
type: "purchase",
count: 100,
});
const refunds = events.derive(purchases, {
type: "refund",
probability: 0.05, // 5% of purchases
delay: { min: 1, max: 30, unit: "days" },
});
const events = events.generate({
type: "order",
count: 100,
template: (fab, timestamp) => ({
id: fab.string.uuid(),
timestamp,
amount: fab.random({ min: 10, max: 500, precision: 2 }),
items: fab.random({ min: 1, max: 5, integer: true }),
}),
});
const user = {
name: fab.person.fullName(),
email: fab.internet.email(),
// Include phone ~24% of time
...(fab.random() < CHANCE.UNCOMMON && {
phone: fab.phone.number(),
}),
};
const status = fab.helpers.weightedArrayElement([
{ value: "active", weight: 0.7 },
{ value: "pending", weight: 0.2 },
{ value: "inactive", weight: 0.1 },
]);

Access underlying Faker modules:

const fab = fabricator("seed");
fab.person.firstName();
fab.person.lastName();
fab.internet.email();
fab.internet.url();
fab.date.recent();
fab.date.past();
fab.string.uuid();
fab.number.int({ min: 1, max: 100 });
describe("User Service", () => {
const fab = fabricator("test-seed");
it("creates user", async () => {
const userData = {
name: fab.person.fullName(),
email: fab.internet.email(),
};
const user = await createUser(userData);
expect(user.name).toBe(userData.name);
});
});