Skip to content

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 →