@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.
Maintainers
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 examplesRun an individual example when you only need one scenario:
npm run example:composition
npm run example:syntheticexample: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
choiceswhen 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
seedfor replayable random values, and pair it withwithClockormock.clock.setClockwhen templates usenowortoday. - 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'], andjsonusually belongs last. - Prefer
slugfor URL/path labels andcompactfor 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 shapeUse 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 objectFor 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.
