Mock Data Generator — JavaScript source
Generate deterministic fake data (names, emails, numbers, dates, booleans, UUIDs, pick-from-list) from a schema and a seed. Reproducible output.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* mock-data-generator - JavaScript polyglot showcase port.
*
* CosmoDev polyglot showcase port of mock-data-generator.
* Ported from src/lib/mockData.ts (the canonical TypeScript reference).
*
* Pure mock-data generation - deterministic and seed-stable. Feeding identical
* (schema, count, seed) always produces byte-identical output, so the same seed
* reproduces a fixture exactly. There is no Math.random / Date.now side-effect:
* the only source of "randomness" is a seeded mulberry32 PRNG, which is fast but
* NOT cryptographically secure - mock data is not a secret, so speed wins.
*
* This is the reference language: the bit-tricks below (|0, >>>, Math.imul) are
* the source of truth that the other ports emulate with explicit 32-bit masking.
*
* Display source - part of CosmoDev's polyglot tool pages.
*
* Usage:
* import { generateMock } from './javascript.js';
* const rows = generateMock(
* [{ name: 'id', type: 'index' }, { name: 'email', type: 'email' }],
* 5,
* 1234,
* );
*/
/**
* The kinds of values a field can synthesize.
* @typedef {"firstName"|"lastName"|"fullName"|"email"|"username"|"integer"
* |"number"|"boolean"|"uuid"|"date"|"pick"|"string"|"index"} FieldType
*/
/**
* A single column declaration.
* @typedef {Object} FieldSpec
* @property {string} name Output key (sanitized to identifier chars).
* @property {FieldType} type Generator to use for this column.
* @property {number} [min] integer/username/number lower bound.
* @property {number} [max] integer/username/number upper bound.
* @property {string} [from] date range start (ISO YYYY-MM-DD).
* @property {string} [to] date range end (ISO YYYY-MM-DD).
* @property {string[]} [options] pick: the candidate strings.
* @property {number} [length] string: character count.
*/
const FIRST_NAMES = [
'Ava', 'Liam', 'Noah', 'Emma', 'Olivia', 'Aiden', 'Sophia', 'Mason', 'Isabella',
'Lucas', 'Mia', 'Ethan', 'Amelia', 'Leo', 'Harper', 'Ezra', 'Ella', 'Owen',
'Luna', 'Finn', 'Zoe', 'Jude', 'Nora', 'Kai', 'Ruby', 'Theo', 'Ivy', 'Max',
];
const LAST_NAMES = [
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis',
'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson',
'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin', 'Lee', 'Perez', 'Thompson',
'White', 'Harris', 'Sanchez', 'Clark', 'Ramirez',
];
/**
* mulberry32 PRNG factory.
*
* Returns a closure that yields successive floats in [0, 1). The state lives in
* the captured `a`, so each call advances the sequence - call it once per value.
* All arithmetic is forced into 32-bit integer semantics via |0 (signed wrap)
* and >>> (unsigned shift); Math.imul is 32-bit multiply. This is what makes the
* stream reproducible across runs (and across the sibling language ports, which
* emulate these exact bit operations).
*
* @param {number} seed Any number; coerced to an unsigned 32-bit integer.
* @returns {() => number}
*/
export function mulberry32(seed) {
let a = seed >>> 0;
return function () {
a |= 0;
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Resolve [min, max] into a sane ordered integer range.
*
* Missing/non-finite bounds fall back to the supplied defaults (0/100 for
* integers, 1/99 for usernames). The result is always lo <= hi so the caller's
* `min + floor(rng * span)` never inverts.
*
* @param {number} min
* @param {number} max
* @returns {{min: number, max: number}}
*/
function clampInt(min, max) {
const lo = Number.isFinite(min) ? Math.floor(min) : 0;
const hi = Number.isFinite(max) ? Math.floor(max) : 100;
return { min: Math.min(lo, hi), max: Math.max(lo, hi) };
}
/**
* Build a v4-shaped UUID purely from the PRNG stream.
*
* Sets the version nibble (position 12 -> '4') and one variant nibble
* (position 16 -> 8/9/a/b) so the result parses as a legal RFC 4122 v4 string,
* even though the bytes are deterministic, not secret.
*
* @param {() => number} rng
* @returns {string}
*/
function uuidFromRng(rng) {
const hex = [];
for (let i = 0; i < 32; i++) {
hex.push(Math.floor(rng() * 16).toString(16));
}
hex[12] = '4';
hex[16] = ['8', '9', 'a', 'b'][Math.floor(rng() * 4)];
return `${hex.slice(0, 8).join('')}-${hex.slice(8, 12).join('')}-${hex
.slice(12, 16)
.join('')}-${hex.slice(16, 20).join('')}-${hex.slice(20, 32).join('')}`;
}
/**
* Coerce a user-supplied column name into an identifier-safe key.
* Strips everything outside [A-Za-z0-9_$]; an all-stripped name becomes fieldN.
*
* @param {string} name
* @param {number} idx Position in the schema, used for the fallback key.
* @returns {string}
*/
function sanitizeFieldName(name, idx) {
const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '');
return cleaned || `field${idx}`;
}
/**
* Generate `count` rows of mock data from a schema.
*
* The PRNG is seeded once and then consumed left-to-right, row by row and field
* by field, so the consumption order is what pins reproducibility: changing any
* earlier field shifts every later value. Negative/NaN counts collapse to 0.
*
* @param {FieldSpec[]} schema
* @param {number} count
* @param {number} seed
* @returns {Record<string, unknown>[]}
*/
export function generateMock(schema, count, seed) {
const rng = mulberry32(seed >>> 0);
const rows = [];
const n = Math.max(0, Math.floor(count));
for (let i = 0; i < n; i++) {
const row = {};
for (let f = 0; f < schema.length; f++) {
const spec = schema[f];
const key = sanitizeFieldName(spec.name, f);
row[key] = generateField(spec, i, rng);
}
rows.push(row);
}
return rows;
}
/**
* Produce one value for one field. Each branch consumes a fixed number of PRNG
* draws, which keeps the stream aligned across rows.
*
* @param {FieldSpec} spec
* @param {number} index Zero-based row index (also the `index` field's value).
* @param {() => number} rng
* @returns {unknown}
*/
function generateField(spec, index, rng) {
switch (spec.type) {
case 'index':
return index;
case 'firstName':
return FIRST_NAMES[Math.floor(rng() * FIRST_NAMES.length)];
case 'lastName':
return LAST_NAMES[Math.floor(rng() * LAST_NAMES.length)];
case 'fullName':
return `${FIRST_NAMES[Math.floor(rng() * FIRST_NAMES.length)]} ${
LAST_NAMES[Math.floor(rng() * LAST_NAMES.length)]
}`;
case 'username': {
const first = FIRST_NAMES[Math.floor(rng() * FIRST_NAMES.length)].toLowerCase();
const { min, max } = clampInt(spec.min ?? 1, spec.max ?? 99);
const num = min + Math.floor(rng() * (max - min + 1));
return `${first}${num}`;
}
case 'email': {
const first = FIRST_NAMES[Math.floor(rng() * FIRST_NAMES.length)].toLowerCase();
const last = LAST_NAMES[Math.floor(rng() * LAST_NAMES.length)].toLowerCase();
return `${first}.${last}@example.com`;
}
case 'integer': {
const { min, max } = clampInt(spec.min ?? 0, spec.max ?? 100);
return min + Math.floor(rng() * (max - min + 1));
}
case 'number': {
const min = Number.isFinite(spec.min) ? spec.min : 0;
const max = Number.isFinite(spec.max) ? spec.max : 1;
const lo = Math.min(min, max);
const hi = Math.max(min, max);
// Round to 4 decimals so mock floats stay tidy and reproducible.
return Math.round((lo + rng() * (hi - lo)) * 10000) / 10000;
}
case 'boolean':
return rng() < 0.5;
case 'uuid':
return uuidFromRng(rng);
case 'date': {
const from = Date.parse(spec.from || '2000-01-01');
const to = Date.parse(spec.to || '2025-12-31');
const lo = Math.min(from, to);
const hi = Math.max(from, to);
const ms = lo + Math.floor(rng() * (hi - lo));
return new Date(ms).toISOString().slice(0, 10);
}
case 'pick': {
const opts = spec.options ?? [];
if (opts.length === 0) return null;
return opts[Math.floor(rng() * opts.length)];
}
case 'string': {
const len = Math.max(1, Math.floor(spec.length ?? 8));
const chars = 'abcdefghijklmnopqrstuvwxyz';
let s = '';
for (let i = 0; i < len; i++) s += chars[Math.floor(rng() * chars.length)];
return s;
}
default:
return null;
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →