npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@joxnathan/mock-randomizer

v0.4.0

Published

fills a structure with randomized data to mock a service or other model provider. number of items in a collection of objects can also be random.

Readme

Mock Randomizer

fills a structure with randomized data to mock a service or other model provider. number of items in a collection of objects can also be random.

Installation

npm install @joxnathan/mock-randomizer -D

Usage

JSON DSL v2 is the preferred way to describe generated values. Put a $mock object anywhere a synthetic value should be created, then pass generation controls such as range, nested array ranges, and seed through the options object.

import mock from '@joxnathan/mock-randomizer';

const changeHistoryTemplate = {
  Id: { $mock: 'guid' },
  TimeStamp: { $mock: 'date', min: 'now', max: 'now' },
  User: {
    Id: { $mock: 'number', min: 100, max: 501 },
    Name: { $mock: 'string', size: 8 },
    Email: { $mock: 'email' }
  },
  Data: [{
    Id: { $mock: 'number', min: 1000, max: 2001 },
    ChangeType: { $mock: 'choose', choices: ['Insert', 'Update', 'Delete'] },
    TableName: { $mock: 'choose', choices: ['Claimant', 'Processor', 'Advisor', 'Supervisor', 'Client', 'Vendor'] },
    RecordId: { $mock: 'number', min: 10, max: 41 },
    FieldName: { $mock: 'choose', choices: ['Id', 'Name', 'Address', 'City', 'State', 'Zip'] },
    PrevValue: '{}',
    CurrValue: '{}'
  }]
};

const detailTemplate = {
  Id: { $mock: 'guid' },
  Version: { $mock: 'ver' },
  Name: { $mock: 'string' },
  Value: { $mock: 'string', minLength: 4, maxLength: 6, case: 'upper' },
  FullName: { $mock: 'fullname' },
  FirstName: { $mock: 'firstname' },
  LastName: { $mock: 'lastname' },
  Company: { $mock: 'company' },
  Street: { $mock: 'street' },
  City: { $mock: 'city' },
  State: { $mock: 'state' },
  Zip: { $mock: 'zip' },
  Tz: { $mock: 'tz' },
  Phone: { $mock: 'phone' },
  Email: { $mock: 'email' },
  Book: { $mock: 'title' },
  TypeSet: { $mock: 'typeset' },
  Locations: [{ $mock: 'city' }],
  HasApproval: { $mock: 'boolean' },
  CreatedOn: { $mock: 'date', min: 'today-12M', max: 'now-1M' },
  CreatedBy: { $mock: 'string' },
  ModifiedOn: { $mock: 'date', min: 'today-7D', max: 'now' },
  ModifiedBy: { $mock: 'string' }
};

this.history.GetChangeHistory = () => Promise.resolve(
  mock.filledList(changeHistoryTemplate, {
    range: { min: 11, max: 75 },
    Data: { min: 1, max: 4 },
    seed: 'history:change-history:v2:case-001'
  }).map(entry => {
    const getData = () => JSON.stringify(mock.filledObject(detailTemplate, {
      Locations: { min: 1, max: 3 }
    }));
    for (const data of entry.Data ?? []) {
      const vals = {
        prev: ['Delete', 'Update'].includes(data.ChangeType) ? getData() : undefined,
        curr: ['Insert', 'Update'].includes(data.ChangeType) ? getData() : undefined
      };
      data.PrevValue = vals.prev;
      data.CurrValue = vals.curr;
    }
    return entry;
  })
);

The generated value is a list of change-history entries. With the sample options above, the top-level list contains 11-75 records and each record's Data array contains 1-4 entries. Generated IDs, dates, names, and nested detail values vary by seed.

// Expected shape:
// [
//   {
//     Id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
//     TimeStamp: Date,
//     User: { Id: 100-500, Name: /^[A-Z]{8}$/, Email: /^[^@\s]+@[^@\s]+\.[^@\s]+$/ },
//     Data: [
//       {
//         Id: 1000-2000,
//         ChangeType: 'Insert' | 'Update' | 'Delete',
//         TableName: 'Claimant' | 'Processor' | 'Advisor' | 'Supervisor' | 'Client' | 'Vendor',
//         RecordId: 10-40,
//         FieldName: 'Id' | 'Name' | 'Address' | 'City' | 'State' | 'Zip',
//         PrevValue: undefined | '{...}',
//         CurrValue: undefined | '{...}'
//       }
//     ]
//   }
// ]

RunKit Usage

const mock = require("@joxnathan/mock-randomizer");

const records = mock.filledList({
  Id: { $mock: 'guid' },
  Name: { $mock: 'fullname' },
  Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] },
  CreatedOn: { $mock: 'date', min: 'today-7D', max: 'now' }
}, {
  range: { min: 2, max: 4 },
  seed: 'runkit:sample:v2'
});

console.log({ records });

// Expected shape:
// {
//   records: [
//     {
//       Id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
//       Name: string,
//       Status: 'New' | 'Open' | 'Closed',
//       CreatedOn: Date
//     }
//   ]
// }
// The list length is 2-4. The seed makes the sample replayable.

Runnable Examples

The repository includes runnable examples that exercise the built package entry point. They are useful as copy/paste starting points and as small smoke tests for end-to-end behavior.

npm run examples

Run an individual example when you only need one scenario:

npm run example:composition
npm run example:synthetic

example:composition shows JSON DSL composition, record-local $vars, transforms, ObjectId-style identifiers, compact IDs, and native bigint output. example:synthetic shows scoped synthetic references and a composed label derived from the selected related record.

JSON DSL v2

JSON DSL v2 is the current template language for new work. A JSON instruction is an object with $mock; the current DSL version is used by default. Version metadata is only needed for legacy compatibility, and when it is needed it belongs once in the top-level fill or validation options rather than on individual fields. Mixed per-field versions are rejected.

const record = mock.filledObject({
  Id: { $mock: 'guid' },
  Name: { $mock: 'fullname', gender: 'female' },
  Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] },
  Score: { $mock: 'number', min: 80, max: 101, inclusive: true },
  Code: { $mock: 'string', minLength: 4, maxLength: 6, case: 'upper' },
  CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
});

// Expected shape:
// {
//   Id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
//   Name: string,
//   Status: 'New' | 'Open' | 'Closed',
//   Score: 80-101,
//   Code: /^[A-Z]{4,6}$/,
//   CreatedOn: Date
// }

Common v2 instructions include:

{ $mock: 'guid' }
{ $mock: 'number', min: 1, max: 100, inclusive: true }
{ $mock: 'bigint', min: '9007199254740993', max: '9007199254741000' }
{ $mock: 'date', min: 'today-7D', max: 'now' }
{ $mock: 'string', minLength: 4, maxLength: 12, case: 'mixed' }
{ $mock: 'choose', choices: ['New', 'Open', 'Closed'] }
{ $mock: 'fullname', gender: 'female' }
{ $mock: 'email' }
{ $mock: 'phone', state: 'AZ' }

JSON DSL objects are validated before generation. Unknown fields, unsupported $mock types, invalid enum values, empty choice lists, invalid ranges, and conflicting aliases throw DSLParseError with a stable code and path.

Built-in Generators

The current JSON DSL v2 generator names are listed below. Where a generator accepts aliases, prefer the first form shown for new templates.

| $mock | Output | Options | | --- | --- | --- | | number | Integer from min inclusive to max exclusive by default. | min, max, inclusive | | bigint | Native JavaScript bigint from min inclusive to max exclusive by default. Use integer strings for values above Number.MAX_SAFE_INTEGER. | min, max, inclusive | | boolean | Random boolean, or a fixed boolean when value is provided. | value | | date | JavaScript Date between two bounds. Named expressions include now, today, min, max, and offsets such as today-7D or now+1h. | min, max; aliases: from, to, start, end, sdate, edate | | string | Alphabetic string. format replaces unescaped x characters with generated letters. | size or length; minLength/maxLength; aliases: min, max, range; case, format | | numstring | Numeric string. format replaces unescaped x characters with generated digits. | size or length; minLength/maxLength; aliases: min, max, range; format | | choose | One value from a list. Array choices preserve commas inside values. | choices; aliases: values, from | | guid | GUID/UUID-style string. | none | | fullname | First, middle, and last name. | gender: male, female, unspecified | | firstname | First name. | gender: male, female, unspecified | | lastname | Last name. | gender is accepted for option consistency; last names are not gender-specific | | company | Company-style name. | none | | street | US-style street address. | format: full, abbrev | | city | City name. | none | | state | US state abbreviation or full state name. | format: full, abbrev | | zip | US ZIP code. | state, format: standard, plusfour | | tz | US time zone. | state, format: full, abbrev, name | | phone | US phone number. | state | | email | Email address. | none | | title | Book/title-style phrase. | none | | typeset | Lorem ipsum-style paragraph text. | none | | ver | App version string, usually major.minor, with optional patch/build parts. | none | | luhn | Numeric string with a valid Luhn check digit. | size or length, format | | compose | String assembled from literal values, nested JSON DSL instructions, variables, and transforms. | parts, separator, format, vars, transform |

Examples:

const sample = mock.filledObject({
  Score: { $mock: 'number', min: 80, max: 101 },
  LargeCounter: { $mock: 'bigint', min: '9007199254740993', max: '9007199254741000' },
  FixedFlag: { $mock: 'boolean', value: true },
  DueAt: { $mock: 'date', min: 'today', max: 'today+1h' },
  Code: { $mock: 'string', length: 8, case: 'upper' },
  Pin: { $mock: 'numstring', length: 6 },
  CardLikeValue: { $mock: 'luhn', size: 14, format: '4xxxxxxxxxxxxxx' },
  Zip: { $mock: 'zip', state: 'AZ', format: 'standard' },
  TimeZone: { $mock: 'tz', state: 'AZ', format: 'abbrev' },
  Version: { $mock: 'ver' },
  Paragraph: { $mock: 'typeset' }
});

// Expected shape:
// {
//   Score: 80-100,
//   LargeCounter: bigint between 9007199254740993n and 9007199254740999n,
//   FixedFlag: true,
//   DueAt: Date,
//   Code: /^[A-Z]{8}$/,
//   Pin: /^\d{6}$/,
//   CardLikeValue: /^4\d{15}$/ and Luhn-valid,
//   Zip: ZIP code valid for AZ,
//   TimeZone: 'MST' | 'MDT',
//   Version: semantic-ish version string,
//   Paragraph: lorem-style text
// }

JSON DSL Composition

Use $mock: 'compose' when a generated value is made from multiple literal and generated parts. parts joins values in order, with an optional separator.

const invoice = mock.filledObject({
  InvoiceNumber: {
    $mock: 'compose',
    parts: [
      { $mock: 'choose', choices: ['INV', 'ORD', 'PAY'] },
      { $mock: 'number', min: 1000, max: 10000 }
    ],
    separator: '-'
  }
}, {
  seed: 'invoice-number:v1'
});

// Expected shape:
// { InvoiceNumber: /^(INV|ORD|PAY)-\d{4}$/ }
// Example: { InvoiceNumber: 'ORD-4821' }

Use format and vars when named generated values need to appear in specific positions. Variables can be literal scalar values or nested JSON DSL instructions.

const user = mock.filledObject({
  Email: {
    $mock: 'compose',
    format: '{first}.{last}@example.test',
    vars: {
      first: { $mock: 'firstname' },
      last: { $mock: 'lastname' }
    },
    transform: ['lower', 'compact']
  }
}, {
  seed: 'user-email:v1'
});

// Expected shape:
// { Email: '[email protected]' }
// Example: { Email: '[email protected]' }
// `lower` normalizes generated casing; `compact` removes whitespace.

Use record-local $vars when multiple fields in the same generated object need to share the same generated pieces. $vars is not emitted in the filled output. Variables are resolved in declaration order, so a variable can use earlier variables from the same $vars object. In a list, each generated item gets its own $vars values.

const user = mock.filledObject({
  $vars: {
    first: { $mock: 'firstname' },
    last: { $mock: 'lastname' },
    full: { $mock: 'compose', format: '{first} {last}' }
  },
  FirstName: { $mock: 'compose', format: '{first}' },
  LastName: { $mock: 'compose', format: '{last}' },
  FullName: { $mock: 'compose', format: '{full}' },
  Email: {
    $mock: 'compose',
    format: '{first}.{last}@example.test',
    transform: ['lower', 'compact']
  }
}, {
  seed: 'shared-user-name:v1'
});

// Expected shape:
// {
//   FirstName: 'Ada',
//   LastName: 'Lovelace',
//   FullName: 'Ada Lovelace',
//   Email: '[email protected]'
// }
// The actual names vary by seed, but all four fields reuse the same generated first/last pair.

Field-local vars override record-local $vars for that one compose instruction.

Composition supports lower, upper, trim, slug, compact, urlEncode, and json transforms. Transforms run after the parts or format string has been resolved.

const ticket = mock.filledObject({
  Slug: {
    $mock: 'compose',
    format: '{priority} {team} {id}',
    vars: {
      priority: { $mock: 'choose', choices: ['Priority'] },
      team: { $mock: 'choose', choices: ['North West'] },
      id: { $mock: 'number', min: 10, max: 100 }
    },
    transform: 'slug'
  }
});

// Expected shape:
// { Slug: /^priority-north-west-\d{2}$/ }
// Example: { Slug: 'priority-north-west-42' }

Transform details and edge cases:

| Transform | Result | | --- | --- | | lower | Converts the final composed string to lowercase. | | upper | Converts the final composed string to uppercase. | | trim | Removes leading and trailing whitespace only. | | slug | Trims, lowercases, replaces runs of non-alphanumeric characters with -, and removes leading/trailing -. | | compact | Removes whitespace only; punctuation such as -, _, ., and @ stays in place. | | urlEncode | Applies encodeURIComponent to the final string. | | json | Applies JSON.stringify to the final value, so a composed string includes JSON quotes. |

const transformed = mock.filledObject({
  Slug: {
    $mock: 'compose',
    parts: ['  Priority North West #10  '],
    transform: 'slug'
  },
  Compact: {
    $mock: 'compose',
    parts: [' A B ', { $mock: 'choose', choices: ['  C  '] }],
    transform: ['compact', 'upper']
  },
  UrlSafe: {
    $mock: 'compose',
    parts: ['Ada [email protected]'],
    transform: 'urlEncode'
  },
  JsonString: {
    $mock: 'compose',
    parts: ['A B'],
    transform: 'json'
  }
});

// Expected output:
// {
//   Slug: 'priority-north-west-10',
//   Compact: 'ABC',
//   UrlSafe: 'Ada%20Lovelace%40example.test',
//   JsonString: '"A B"'
// }

Compose instructions are validated with the rest of JSON DSL v2. Empty parts, unknown format placeholders, invalid transforms, unsupported nested instruction fields, invalid $vars, and invalid nested ranges report stable error codes with paths such as Email.vars.first.choices, $vars.full.vars, or InvoiceNumber.parts[1].max.

Composing Non-built-in Identifiers

When a data type is not a named generator, build it from compose, choose, string, numstring, and transforms. This keeps the DSL small while still covering application-specific identifiers.

For a strict MongoDB BSON ObjectId-style value, generate 24 lowercase hexadecimal characters. This recipe matches the 24-hex shape; it does not encode MongoDB's timestamp, process, or counter semantics.

const hex = ['0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
const hexChar = { $mock: 'choose', choices: hex };

const doc = mock.filledObject({
  _id: {
    $mock: 'compose',
    parts: Array.from({ length: 24 }, () => hexChar)
  }
}, {
  seed: 'mongo-object-id-style:v1'
});

// Expected shape:
// { _id: /^[0-9a-f]{24}$/ }
// Example: { _id: '507f1f77bcf86cd799439011' }
// The example value is illustrative; seeded output depends on the random stream.

In JSON template files, expand parts to 24 entries because JSON cannot call Array.from. For shorter JSON files, use a looser shape such as { "$mock": "string", "length": 24, "case": "lower" } only when alphabetic characters are acceptable; that is not a strict hexadecimal ObjectId shape.

For compact application IDs, compose fixed prefixes with generated parts.

const order = mock.filledObject({
  OrderId: {
    $mock: 'compose',
    parts: [
      'ORD',
      { $mock: 'choose', choices: ['2026'] },
      { $mock: 'numstring', length: 6 }
    ],
    separator: '-',
    transform: ['compact', 'upper']
  }
}, {
  seed: 'order-id:v1'
});

// Expected shape:
// { OrderId: /^ORD-2026-\d{6}$/ }
// Example: { OrderId: 'ORD-2026-104928' }
// `compact` removes whitespace; it intentionally keeps the hyphen separator.

For SKU-like values, use format and variables.

const sku = mock.filledObject({
  Sku: {
    $mock: 'compose',
    format: '{dept}-{family}-{code}',
    vars: {
      dept: { $mock: 'choose', choices: ['HW', 'SW', 'OPS'] },
      family: { $mock: 'string', length: 3, case: 'upper' },
      code: { $mock: 'numstring', length: 5 }
    }
  }
});

// Expected shape:
// { Sku: /^(HW|SW|OPS)-[A-Z]{3}-\d{5}$/ }
// Example: { Sku: 'HW-QRM-57201' }

Template-authoring notes:

  • Use exact choices when documentation, tests, or agent-generated templates need stable examples.
  • Use shape comments for random output. Regexes, ranges, and "one of" comments are clearer than pretending the generated sample is literal.
  • Use seed for replayable random values, and pair it with withClock or mock.clock.setClock when templates use now or today.
  • In JSON files, expand helper-built arrays such as the 24 ObjectId hex parts; JSON cannot run Array.from.
  • Remember transform order matters. ['trim', 'upper'] is not the same as ['upper', 'json'], and json usually belongs last.
  • Prefer slug for URL/path labels and compact for identifiers that should remove spaces without removing punctuation.

Synthetic Data Layer

The synthetic layer adds preflight validation and reusable dataset definitions on top of JSON DSL templates. Use validateTemplate when you want a report instead of a thrown generation error.

const validation = mock.synthetic.validateTemplate({
  User: {
    Email: { $mock: 'email' }
  },
  Orders: [{
    Total: { $mock: 'number', min: 10, max: 1 }
  }]
});

if (!validation.valid) {
  console.log(validation.issues);
  // [{ code: 'invalid_range', path: 'Orders[0].Total.max', ... }]
}

Use compileTemplate when a template should be checked once and reused many times.

const userTemplate = mock.synthetic.compileTemplate({
  Id: { $mock: 'guid' },
  Name: { $mock: 'fullname' },
  Email: { $mock: 'email' },
  Status: { $mock: 'choose', choices: ['New', 'Active', 'Disabled'] }
});

const user = userTemplate.fill({ seed: 'users:v2:case-001' });
const users = userTemplate.fillList({
  seed: 'users:v2:list-001',
  range: { min: 10, max: 10 }
});

// Expected shape:
// user: { Id: GUID, Name: string, Email: /^[^@\s]+@[^@\s]+\.[^@\s]+$/, Status: 'New' | 'Active' | 'Disabled' }
// users: 10 generated records with the same field shape

Use generateSyntheticData for simple entity/table style datasets. Dataset seeds are expanded per entity so each table has a deterministic but separate generated stream.

const data = mock.synthetic.generateSyntheticData({
  seed: 'claims-sandbox:v2',
  entities: {
    Users: {
      template: userTemplate.template,
      options: { range: { min: 25, max: 25 } }
    },
    AuditEvents: {
      template: {
        Id: { $mock: 'guid' },
        EventType: { $mock: 'choose', choices: ['Created', 'Updated', 'Deleted'] },
        Actor: { $mock: 'fullname' }
      },
      options: { range: { min: 100, max: 100 } }
    }
  }
});

// Expected shape:
// {
//   Users: [{ Id: GUID, Name: string, Email: string, Status: 'New' | 'Active' | 'Disabled' }, ...25],
//   AuditEvents: [{ Id: GUID, EventType: 'Created' | 'Updated' | 'Deleted', Actor: string }, ...100]
// }

Synthetic references let later entities point at records generated by earlier entities. By default, $ref picks a deterministic random record from the target entity, then returns the requested path.

const data = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' },
        Email: { $mock: 'email' }
      },
      options: { range: { min: 10, max: 10 } }
    },
    Claims: {
      template: {
        Id: { $mock: 'guid' },
        AssignedUserId: { $ref: 'Users', path: 'Id' },
        AssignedUserEmail: { $ref: 'Users.Email' }
      },
      options: { range: { min: 50, max: 50 } }
    }
  }
});

// Expected shape:
// {
//   Users: [{ Id: GUID, Name: string, Email: string }, ...10],
//   Claims: [{ AssignedUserId: GUID, AssignedUserEmail: string }, ...50]
// }
// Without a shared `scope`, AssignedUserId and AssignedUserEmail may come from different Users rows.

Use index when a scenario needs an exact fixture row. Indexes are zero-based and must exist in the generated entity list.

const claim = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2:indexed',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' }
      },
      options: { range: { min: 4, max: 4 } }
    },
    Claims: {
      template: {
        ReviewerName: { $ref: 'Users', index: 3, path: 'Name' },
        SameReviewerName: { $ref: 'Users[3].Name' }
      },
      options: { range: { min: 1, max: 1 } }
    }
  }
}).Claims[0];

// Expected output:
// {
//   ReviewerName: Users[3].Name,
//   SameReviewerName: Users[3].Name
// }
// Both fields are equal because both references target Users[3].Name.

References only point backward to entities that have already been generated. Missing entities, forward references, empty target lists, out-of-range indexes, and missing paths throw stable synthetic reference errors.

Use scope when multiple fields in the same generated record should come from the same selected related record. The scope is local to each generated root record, so each claim below gets one deterministic assigned user while AssignedUserId, AssignedUserName, and AssignedUserEmail stay consistent with each other.

const data = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2:scoped',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' },
        Email: { $mock: 'email' }
      },
      options: { range: { min: 10, max: 10 } }
    },
    Claims: {
      template: {
        AssignedUserId: { $ref: 'Users', scope: 'assignedUser', path: 'Id' },
        AssignedUserName: { $ref: 'Users', scope: 'assignedUser', path: 'Name' },
        AssignedUserEmail: { $ref: 'Users.Email', scope: 'assignedUser' }
      },
      options: { range: { min: 50, max: 50 } }
    }
  }
});

// Expected shape:
// Claims: [
//   {
//     AssignedUserId: same Users[n].Id,
//     AssignedUserName: same Users[n].Name,
//     AssignedUserEmail: same Users[n].Email
//   },
//   ...
// ]

References that share a scope must target the same entity. They must also either all omit index or all use the same explicit index, so scoped references do not depend on traversal order.

Composition can use synthetic references too. Reference-backed compose values are rendered after references are resolved, so the selected related record stays consistent across IDs, labels, emails, and other derived fields.

const data = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2:composed-ref',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' },
        Email: { $mock: 'email' }
      },
      options: { range: { min: 10, max: 10 } }
    },
    Claims: {
      template: {
        AssignedUserId: { $ref: 'Users', scope: 'assignedUser', path: 'Id' },
        AssignedUserLabel: {
          $mock: 'compose',
          format: '{name} <{email}>',
          vars: {
            name: { $ref: 'Users', scope: 'assignedUser', path: 'Name' },
            email: { $ref: 'Users.Email', scope: 'assignedUser' }
          }
        }
      },
      options: { range: { min: 50, max: 50 } }
    }
  }
});

// Expected shape:
// Claims: [
//   {
//     AssignedUserId: same Users[n].Id,
//     AssignedUserLabel: 'Users[n].Name <Users[n].Email>'
//   },
//   ...
// ]

Record-local $vars can also hold references when used inside synthetic datasets.

const data = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2:composed-ref-vars',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' },
        Email: { $mock: 'email' }
      },
      options: { range: { min: 10, max: 10 } }
    },
    Claims: {
      template: {
        $vars: {
          userId: { $ref: 'Users', scope: 'assignedUser', path: 'Id' },
          name: { $ref: 'Users', scope: 'assignedUser', path: 'Name' },
          email: { $ref: 'Users.Email', scope: 'assignedUser' },
          label: { $mock: 'compose', format: '{name} <{email}>' }
        },
        AssignedUserId: { $mock: 'compose', format: '{userId}' },
        AssignedUserLabel: { $mock: 'compose', format: '{label}' }
      },
      options: { range: { min: 50, max: 50 } }
    }
  }
});

// Expected shape:
// Claims: [
//   {
//     AssignedUserId: same Users[n].Id,
//     AssignedUserLabel: 'Users[n].Name <Users[n].Email>'
//   },
//   ...
// ]
// `$vars` is only a local generation aid; it is not emitted in each Claim.

Do not write references with v1-style strings. A value such as "$ref:Users.Id" is rejected during validation. Use JSON object form instead:

{ "$ref": "Users", "path": "Id" }

Templates can also be loaded from JSON files. Relative paths are resolved from the current project directory unless baseDir is provided.

const template = await mock.loadTemplate('./templates/user.json');
const compiled = await mock.loadCompiledTemplate('./templates/user.json');

const user = compiled.fill({ seed: 'users:v2:case-001' });

Dataset files can reference template files with paths relative to the dataset file.

const data = await mock.loadSyntheticData('./datasets/claims-sandbox.json', {
  rootDir: '.'
});

Example dataset file:

{
  "seed": "claims-sandbox:v2",
  "entities": {
    "Users": {
      "template": "../templates/user.json",
      "options": { "range": { "min": 25, "max": 25 } }
    },
    "AuditEvents": {
      "template": {
        "Id": { "$mock": "guid" },
        "EventType": { "$mock": "choose", "choices": ["Created", "Updated", "Deleted"] }
      },
      "options": { "range": { "min": 100, "max": 100 } }
    }
  }
}

Absolute disk paths and UNC paths are supported. Use rootDir to restrict where templates may be loaded from. HTTP(S) template loading is disabled by default; set allowNetwork: true only for trusted sources.

Output Adapters

Output adapters turn generated data into plain strings for tests, snapshots, pipeline artifacts, or handoff to database-specific tooling. They do not write files or connect to databases.

const data = mock.generateSyntheticData({
  seed: 'claims-sandbox:v2',
  entities: {
    Users: {
      template: {
        Id: { $mock: 'guid' },
        Name: { $mock: 'fullname' },
        Email: { $mock: 'email' },
        CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
      },
      options: { range: { min: 2, max: 2 } }
    }
  }
});

const prettyJson = mock.toJson(data);
const compactJson = mock.toJson(data, { pretty: false });
const usersNdjson = mock.toNdjson(data, { entity: 'Users' });
const allNdjson = mock.toNdjson(data);
const usersCsv = mock.toCsv(data, 'Users');

JSON output is pretty printed by default and can be made compact with { pretty: false }. Dates are serialized as ISO strings. Native bigint values are serialized as decimal strings so JSON output remains valid and precision is not lost.

NDJSON output can target one entity or the whole dataset. Whole-dataset NDJSON includes the entity name on each line so records from different tables do not lose context.

CSV output targets one entity at a time because each entity can have a different shape. Nested objects and arrays are written as JSON strings inside the cell. Use columns to control field order or project a smaller export.

const csv = mock.toCsv(data, {
  entity: 'Users',
  columns: ['Id', 'Email', 'CreatedOn'],
  delimiter: ','
});

When columns is provided, adapters reject a column that is not present in any selected record. Empty record sets also fail by default. Use allowMissingColumns: true or allowEmpty: true only when that shape is intentional.

Database insert payload adapters are also pure string/object builders. They do not open connections, execute SQL, or write files.

Use toInsertRows when a test database client already handles parameterization.

const rows = mock.toInsertRows(data, {
  entity: 'Users',
  columns: ['Id', 'Email', 'CreatedOn']
});

// example shape for a query builder:
// await db('Users').insert(rows);

Dates are normalized to ISO strings. Nested objects and arrays are JSON strings by default so rows stay scalar-friendly. Set nested: 'preserve' when the target client accepts structured JSON column values.

const jsonColumnRows = mock.toInsertRows(data, {
  entity: 'Users',
  nested: 'preserve'
});

Use toSqlInserts for deterministic non-production fixture scripts. Identifiers are validated before SQL is produced, string values are escaped, and table names must be explicit for raw record arrays.

const sql = mock.toSqlInserts(data, {
  entity: 'Users',
  schema: 'sandbox',
  table: 'UserSeed',
  dialect: 'postgres',
  columns: ['Id', 'Email', 'CreatedOn'],
  batchSize: 100
});

Reproducible Data

Seeded generation is optional. Without a seed, the package continues to use Math.random().

A seed may be a string or number. The simplest good seed is a stable, readable value that describes the test case. Use the same seed with the same template and options to replay the same generated data.

const template = {
  Id: { $mock: 'guid' },
  Name: { $mock: 'fullname' },
  Status: { $mock: 'choose', choices: ['New', 'Open', 'Closed'] }
};

const first = mock.withSeed('case-001', () => mock.filledObject(template));
const second = mock.withSeed('case-001', () => mock.filledObject(template));
// first and second are equal

const third = mock.filledObject(template, { seed: 'case-001' });
// per-call seed injection is also supported from the options object

For most tests, do not overthink the seed. Pick a short value that will stay the same when the test is renamed or moved.

const record = mock.filledObject(template, {
  seed: 'claim-update-happy-path'
});

For larger suites, build seed values from stable business context. This makes failures easier to replay and keeps unrelated tests from accidentally sharing the same generated data stream.

const seed = [
  'claims-api',
  'change-history',
  'v2',
  'update-with-attachments',
  'case-001'
].join(':');

const records = mock.filledList(template, {
  seed,
  range: { min: 10, max: 10 }
});

Good seed values are stable, descriptive, and safe to commit. Avoid Date.now(), random GUIDs, environment-specific paths, database IDs, secrets, or personal data. When the template changes in a meaningful way, include a schema or scenario version such as v2 so old snapshots and new snapshots do not pretend to be the same generated case.

mock.random.setSeed('suite-001');
const value = mock.filledList(template, {
  range: { min: 2, max: 4 }
});
mock.random.clearSeed();

Dates that use now or today can also be made deterministic with clock injection.

const fixedNow = '2024-07-15T16:45:30.005Z';

const dated = mock.withClock(fixedNow, () => mock.filledObject({
  Id: { $mock: 'guid' },
  CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
}, { seed: 'case-001' }));

mock.clock.setClock(fixedNow);
const suiteValue = mock.filledObject({
  CreatedOn: { $mock: 'date', min: 'today', max: 'now' }
});
mock.clock.clearClock();

Legacy String DSL

The old v1 string shorthand exists only as a migration aid for existing templates. It is deprecated, disabled by default, and not recommended for new test suites or seed data. Prefer JSON DSL v2.

To run an old template temporarily, set allowLegacyDsl explicitly:

const sameStyle = mock.filledObject({
  Id: 'guid',
  CreatedOn: 'date:today:now'
}, { allowLegacyDsl: true });

Without that switch, v1 strings throw DSLParseError with code: 'legacy_dsl_disabled'. The error message points to JSON DSL object examples first, then mentions { allowLegacyDsl: true } as the last-resort migration switch.

Legacy v1 strings are scalar value shorthand only. They cannot express JSON DSL metadata, $version, synthetic references, reference indexes, reference paths, dataset entities, entity options, template file paths, loader safety options, or future output adapters. When a dataset uses $ref, validation rejects any remaining v1 string DSL in that dataset with code: 'legacy_dsl_unsupported_feature'; convert those fields to JSON DSL v2 first.

In dataset files, string values in an entity's template field are treated as template file paths. A value like "template": "guid" is rejected with TemplateLoadError code legacy_template_source_unsupported. Use an inline JSON object template instead:

{
  "entities": {
    "Users": {
      "template": {
        "Id": { "$mock": "guid" }
      }
    }
  }
}

Or point to a JSON template file explicitly:

{
  "entities": {
    "Users": {
      "templatePath": "./templates/user.json"
    }
  }
}

Potential Upcoming Features

1. Schema bridges for JSON Schema, Zod, and/or OpenAPI template generation.
2. A CLI with commands such as validate, generate, to-csv, to-sql, and pack.
3. Richer relationship modeling, including one-to-many, many-to-many, uniqueness, required foreign keys, weighted references, and relationship cardinality helpers.
4. Custom generator and plugin support for domain-specific test data.
5. Large-data streaming and batching for bigger non-production database fixture generation.
6. Dialect-specific database payload helpers beyond raw insert statements.
7. Additional examples for Jest/Vitest snapshots, API mock data, SQL seed files, and non-production database seeding.
8. Release confidence improvements such as Bitbucket pipeline badges, coverage badges, and npm provenance/2FA notes.

Changelog

See CHANGELOG.md for release notes, migration notes, and pending changes.

Contributing

In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.