relational-faker
v1.3.0
Published
Deterministic, topologically sorted relational mock data generator.
Maintainers
Readme
RelationalFaker
💡 Why RelationalFaker?
Standard mocking libraries (like faker.js) are excellent for generating scalar values but struggle with relational integrity.
- ❌ The Problem: If you need to generate
Users,Posts, andComments, you have to manually manage the order. You can't generate a Comment before its Post. - ✅ The Solution: RelationalFaker analyzes your schema, builds a Dependency Graph, and uses Topological Sort to automatically determine the correct generation order.
✨ Features
- 🧠 Auto-Dependency Resolution: Just define relations; the engine figures out the execution order.
- 🤝 Many-to-Many Support: Generate unique, non-colliding pairs for join tables (v1.3).
- 💾 Data Exporters: Export generated data to SQL (INSERT statements) or CSV formats (v1.2).
- 🔄 Recursive / Self-Referencing Relations: Support for trees, nested comments, or organizational hierarchies (v1.1).
- 🎯 Smart Constraints: Define rules like "End Date must be after Start Date" (v1.1).
- 🛡️ TypeScript First: Fully typed definitions.
- ⚛️ Deterministic Seeding: Reproducible test runs.
📦 Installation
npm install relational-faker @faker-js/faker
# or
yarn add relational-faker @faker-js/faker🚀 Quick Start
import { RelationalFaker, f } from 'relational-faker';
const db = new RelationalFaker({
users: {
count: 5,
schema: {
id: f.uuid(),
name: f.fullName(),
}
},
tasks: {
count: 20,
schema: {
id: f.uuid(),
// Context-aware generation: Access the store to get current count
title: f.custom((ctx) => `Task #${ctx.store.length + 1}`),
assigneeId: f.relation('users', 'id'),
// ✨ Smart Constraint: Due date is always AFTER created date
createdAt: f.date.past(),
dueAt: f.date.soon(10, 'createdAt'),
}
},
categories: {
count: 10,
schema: {
id: f.uuid(),
name: f.custom(() => "Category"),
// 🔄 Self-Reference: Categories can have parent categories
parentId: f.relation('categories', 'id'),
}
}
});
const data = db.generate();
console.log(data.users);
console.log(data.tasks); // Dates are logically consistent🤝 Many-to-Many Relations (v1.3)
Standard random selection can cause duplicate primary keys in join tables (e.g., assigning the same Student to the same Course twice).
Use crossJoin to generate unique pairs automatically.
import { RelationalFaker, f, crossJoin } from 'relational-faker';
// 1. Create a cross-join generator for Students <-> Courses
const enrollments = crossJoin('students', 'courses');
const db = new RelationalFaker({
students: { count: 3, schema: { id: f.uuid() } },
courses: { count: 3, schema: { id: f.uuid() } },
// Join Table
student_courses: {
count: 5, // Must be <= (students.count * courses.count)
schema: {
// 2. Assign the unique pair generators
studentId: enrollments.left,
courseId: enrollments.right,
enrolledAt: f.date.past()
}
}
});💾 Data Export (v1.2)
You can export the generated data to SQL or CSV for seeding databases or external analysis.
import { RelationalFaker, Exporters } from 'relational-faker';
const db = new RelationalFaker({ /* config */ });
const data = db.generate();
// 1. SQL Export (PostgreSQL, MySQL, SQLite)
// Generates valid INSERT statements handling dates, nulls, and escaping.
const sql = Exporters.toSQL(data, { dialect: 'postgres' });
console.log(sql);
// Output: INSERT INTO "users" ("id", "name") VALUES ...
// 2. CSV Export
// Returns an object where keys are table names and values are CSV strings.
const csvFiles = Exporters.toCSV(data);
console.log(csvFiles['users']);
// Output: id,name,email...📚 API Reference
f.relation(tableName, fieldName)
Creates a foreign key reference. Supports self-referencing (recursive) tables automatically. The engine guarantees that the referenced record exists.
crossJoin(tableA, tableB, fieldA?, fieldB?)
Creates a synchronized pair of generators that yield unique combinations (Cartesian Product) of the two tables.
- Returns
{ left, right }field descriptors.
f.date.soon(days, refField?)
Generates a date in the future relative to refField.
days: Range of days to generate within.refField: Name of another field in the same row (e.g.,'createdAt').
f.custom((context) => T)
Provides access to the generation context for complex logic.
context.row: The current row being generated.context.store: The array of rows generated so far for the current table.context.db: The complete database of previously generated tables.
Exporters.toSQL(data, options?)
Converts the generated data object into a SQL string.
options.dialect:'postgres'(default),'mysql', or'sqlite'.
Exporters.toCSV(data)
Converts the generated data object into a record of CSV strings ({ tableName: csvString }).
🧪 Testing with Seeds
For unit tests, consistency is key. Use .seed() to ensure the same data is generated every time.
const mocker = new RelationalFaker(config);
mocker.seed(12345);
const result = mocker.generate();⚠️ Known Limitations
- Cross-Table Circular Dependencies: While self-references (A -> A) are supported, direct circular loops between two tables (A -> B -> A) are currently detected and blocked to prevent infinite loops.
- Large Datasets: Performance is optimized for typical testing scenarios (up to ~10k records).
🤝 Contributing
Contributions are welcome!
- Fork the project
- Create your feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
📄 License
Distributed under the MIT License.
