@cosyte/synth
@cosyte/synth - a deterministic, seedable synthetic-data / test-fixture generator for the
cosyte healthcare formats. A consumer of the parsers, not a parser: it builds artifacts through
each parser's own builder/serializer (so output is spec-clean by construction) and draws every
value from a guaranteed-non-colliding synthetic source (so no output can be real or
plausibly-real PHI). It is a format/conformance generator, not a clinical simulator.
This root entry point exposes the format-agnostic core: the seeded PRNG, the synthetic-safety
providers, the Corpus abstraction, the profile skeleton, and the fatal codes. Per-format
generation lives behind its own subpath (@cosyte/synth/hl7) so importing the root never pulls a
parser - the lazy per-format boundary.
Classes
SynthError
The typed error every fatal @cosyte/synth condition throws. Carries a stable
SynthFatalCode so callers branch on err.code without matching message text.
It takes no value parameter. The message is whatever SYNTH_FATAL_MESSAGES holds for the
code, so no caller-supplied string can reach a diagnostic surface by any route - not message, not
stack, not a field on the thrown object.
Example
import { SynthError, SYNTH_FATAL_CODES } from "@cosyte/synth";
throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_FORMAT);
Extends
Error
Constructors
Constructor
new SynthError(
code):SynthError
Parameters
code
The stable SynthFatalCode. The message comes from the frozen registry.
Returns
Overrides
Error.constructor
Properties
code
readonlycode:SynthFatalCode
The stable fatal code.
Interfaces
Artifact
One generated artifact - the serialized wire text plus the metadata needed to reproduce and check
it. warnings records what the artifact's own parser reported on the round-trip (zero for a
spec-clean artifact).
Properties
content
readonlycontent:string
The serialized wire text, produced by the parser's own conservative serializer.
format
readonlyformat:SynthFormat
The format this artifact belongs to.
kind
readonlykind:string
A format-specific kind label (e.g. "ADT^A01").
warnings
readonlywarnings: readonlystring[]
The warning codes the parser emitted when the artifact was round-tripped (empty = spec-clean).
Corpus
A reproducible, self-describing set of generated artifacts.
Properties
artifacts
readonlyartifacts: readonlyArtifact[]
The generated artifacts, in generation order.
manifest
readonlymanifest:CorpusManifest
The manifest describing what was generated.
seed
readonlyseed:number
The seed the corpus was generated from - regenerating from it yields byte-identical artifacts.
CorpusManifest
A self-describing manifest of a Corpus.
Properties
counts
readonlycounts:Readonly<Record<string,number>>
Per-kind artifact counts (e.g. { "ADT^A01": 3 }).
formats
readonlyformats: readonlySynthFormat[]
The formats present in the corpus.
quirks
readonlyquirks: readonlystring[]
The quirk names applied.
QuirkArtifact
One generated quirk artifact - the off-spec wire text plus the contract it is meant to satisfy.
Properties
content
readonlycontent:string
The quirked wire text (deterministic in the seed + quirk).
format
readonlyformat:SynthFormat
The format this artifact belongs to.
intendedWarnings
readonlyintendedWarnings: readonlystring[]
The exact parser warning code(s) this artifact is meant to round-trip to.
kind
readonlykind:string
The underlying spec-clean message kind the quirk was injected into (e.g. "ORU^R01").
quirk
readonlyquirk:string
The quirk recipe applied.
QuirkDescriptor
A public, grounded description of one vendor quirk - the metadata that binds a quirk recipe to a real parser warning code and a publicly-groundable deviation (cited-public, never a private vendor corpus).
Properties
disposition
readonlydisposition:QuirkProfileDisposition
How toleratingProfile treats the quirk.
format
readonlyformat:SynthFormat
The format this quirk applies to.
grounding
readonlygrounding:string
The public grounding for this quirk - the spec clause or the parser's public profile that documents the tolerance. Never a private vendor-attributed corpus.
intendedWarnings
readonlyintendedWarnings: readonlystring[]
The exact parser warning code(s) a bare parse (no profile) surfaces for this quirk - the intended-warning contract. A quirk that produces any other code, or none, is a generation bug.
name
readonlyname:string
The quirk recipe name (e.g. "unknown-zsegment"). Stable; part of the public contract.
toleratingProfile?
readonlyoptionaltoleratingProfile?:string
The parser profile that tolerates this quirk (when a built-in public one exists).
QuirkProfiledVerdict
The verdict of a bare parse under the tolerating profile, if any.
Properties
disposition
readonlydisposition:QuirkProfileDisposition
How the profile treats the quirk.
profileName
readonlyprofileName:string
The profile applied.
tolerated
readonlytolerated:boolean
true iff the profile handled the quirk as its disposition declares: "suppressed" ⇒ the intended
code is gone; "rebadged" ⇒ the intended code is gone and PROFILE_QUIRK_APPLIED is present.
warnings
readonlywarnings: readonlystring[]
The warning codes the parser emitted with the profile active.
QuirkRoundTripResult
The verdict of round-tripping a quirk artifact through its parser.
Properties
content
readonlycontent:string
The quirked wire text that was parsed.
intendedWarningHeld
readonlyintendedWarningHeld:boolean
true iff the bare parse produced exactly the intended code(s) - the intended-warning contract.
intendedWarnings
readonlyintendedWarnings: readonlystring[]
The exact code(s) the quirk is meant to produce.
warnings
readonlywarnings: readonlystring[]
The warning codes a bare parse (no profile) emitted.
withProfile?
readonlyoptionalwithProfile?:QuirkProfiledVerdict
The verdict under the tolerating profile, when a built-in public one exists.
Rng
A seeded, deterministic random source. Created via createRng; passed explicitly to every provider. All draw methods advance the internal state deterministically.
Properties
seed
readonlyseed:number
The integer seed this generator was created from (part of the Corpus manifest).
Methods
bool()
bool(
p?):boolean
true with probability p (default 0.5).
Parameters
p?
number
Returns
boolean
digits()
digits(
n):string
A string of n decimal digits (0–9), each drawn uniformly.
Parameters
n
number
The number of digits (>= 0).
Returns
string
float()
float():
number
The next float in [0, 1).
Returns
number
int()
int(
min,max):number
A uniformly-distributed integer in the inclusive range [min, max].
Parameters
min
number
Inclusive lower bound (integer).
max
number
Inclusive upper bound (integer, >= min).
Returns
number
nextUint32()
nextUint32():
number
The next unsigned 32-bit integer.
Returns
number
pick()
pick<
T>(items):T
Pick one element from a non-empty array.
Type Parameters
T
T
Parameters
items
readonly T[]
A non-empty readonly array.
Returns
T
Sfc32State
The mutable four-word sfc32 state. Threaded explicitly (never global) by ../rng/rng.Rng.
Properties
a
a:
number
State word a.
b
b:
number
State word b.
c
c:
number
State word c.
d
d:
number
Counter word d.
SyntheticAddress
A synthetic postal address - synthetic street + city, a fixed non-real ZIP.
Properties
city
readonlycity:string
A clearly-fake city.
state
readonlystate:string
A US state abbreviation (structural only; never combined with a real street + name + DOB).
street
readonlystreet:string
A clearly-fake street line.
zip
readonlyzip:string
A reserved non-real ZIP (00000).
SyntheticIdentifier
A synthetic identifier scoped to the synthetic assigning authority.
Properties
assigningAuthority
readonlyassigningAuthority:string
The synthetic assigning-authority namespace id.
assigningAuthorityOid
readonlyassigningAuthorityOid:string
The synthetic assigning-authority OID (HL7 example arc).
typeCode
readonlytypeCode:"MR"|"AN"|"MB"
HL7 identifier type code (MR = medical record, AN = account, MB = member).
value
readonlyvalue:string
The identifier value (digits) - unique only within the synthetic namespace.
SyntheticName
A synthetic person name drawn from the shipped fake-name pool.
Properties
family
readonlyfamily:string
A clearly-fake family name.
given
readonlygiven:string
A clearly-fake given name.
SynthProfile
A frozen, validated fixture recipe produced by defineSynthProfile.
Properties
familyNames?
readonlyoptionalfamilyNames?: readonlystring[]
The family-name pool this profile draws from.
givenNames?
readonlyoptionalgivenNames?: readonlystring[]
The given-name pool this profile draws from (overrides or the built-in default).
name
readonlyname:string
The profile name.
quirks
readonlyquirks: readonlystring[]
The requested quirk recipe names.
SynthProfileSpec
The user-authored spec passed to defineSynthProfile.
Properties
familyNames?
readonlyoptionalfamilyNames?: readonlystring[]
Optional family-name pool override (clearly-synthetic names only).
givenNames?
readonlyoptionalgivenNames?: readonlystring[]
Optional given-name pool override (clearly-synthetic names only - see the safety invariant).
name
readonlyname:string
A stable, human-readable profile name (e.g. "acme-hospital"). Required, non-empty.
quirks?
readonlyoptionalquirks?: readonlystring[]
The vendor quirk recipe names this profile requests. Validated against the target format's quirk
registry when the profile drives a quirk corpus (an unsupported quirk is a fatal
SYNTH_UNSUPPORTED_QUIRK, never a silent no-op).
Type Aliases
QuirkProfileDisposition
QuirkProfileDisposition =
"suppressed"|"rebadged"|"bare"
How the parser's matching profile treats a quirk once it is active - the three shapes the parsers' profile systems actually exhibit (verified firsthand against each parser):
"suppressed"- the profile makes the warning disappear (HL7 v2: adefineProfilecustomSegmentsclaim suppressesUNKNOWN_SEGMENTfor a declared Z-segment)."rebadged"- the profile downgrades the warning to the value-freePROFILE_QUIRK_APPLIEDmarker withexpected: true(C-CDAdefineCcdaProfile/ ASTMdefineAstmProfileprofileQuirkApplied)."bare"- no shipped profile tolerates it; the quirk targets a real coded leniency a consumer can tolerate via their owndefineProfile/defineAstmProfile, but no built-in re-badges it.
SsnBlock
SsnBlock =
"never-issued"|"advertising"
Which SSN reserved space to draw from.
SynthFatalCode
SynthFatalCode = typeof
SYNTH_FATAL_CODES[keyof typeofSYNTH_FATAL_CODES]
A value from SYNTH_FATAL_CODES - the type carried by a thrown SynthError.
SynthFormat
SynthFormat =
"hl7v2"|"fhir"|"ccda"|"x12"|"ncpdp"|"astm"
The format an artifact was generated for.
Variables
DEA_REGISTRANT_TYPES
constDEA_REGISTRANT_TYPES: readonlystring[]
The DEA-registration prefix letters @cosyte/synth draws a synthetic DEA number's first character
from. A real DEA number is <registrant-type><last-name-initial> + 7 digits; the first letter is the
registrant type (A/B/F/G/M/P/R/X are the widely-published values; the second letter is the
registrant's last-name initial). These letters are a fact about the number's shape, not
copyrighted prose - they only shape the value; the synthetic guarantee is the deliberately-invalid
checksum (see dea / isSyntheticDea).
DOC_V6_PREFIX
constDOC_V6_PREFIX:"2001:db8"="2001:db8"
RFC 3849 IPv6 documentation prefix.
NPI_LUHN_PREFIX
constNPI_LUHN_PREFIX:"80840"="80840"
The 80840 prefix prepended to a 10-digit NPI before the Luhn check (the CMS NPI check-digit
rule - 80840 is the ISO 7812 issuer identifier for the US health-application namespace). A real
NPI satisfies luhn("80840" + npi) ≡ 0 (mod 10).
PROFILE_QUIRK_APPLIED
constPROFILE_QUIRK_APPLIED:"PROFILE_QUIRK_APPLIED"="PROFILE_QUIRK_APPLIED"
The stable, value-free re-badge code the C-CDA and ASTM parsers emit when a profile tolerates a quirk. HL7 v2 has no equivalent (it suppresses instead - see QuirkProfileDisposition).
RESERVED_EMAIL_DOMAINS
constRESERVED_EMAIL_DOMAINS: readonlystring[]
RFC 2606 / 6761 reserved email domains @cosyte/synth draws from.
safe
constsafe:Readonly<{address: (rng) =>SyntheticAddress;dateYmd: (rng,minYear,maxYear) =>string;dea: (rng,person?) =>string;rng,person?) =>string;identifier: (rng,typeCode) =>SyntheticIdentifier;ipv4: (rng) =>string;ipv6: (rng) =>string;name: (rng) =>SyntheticName;npi: (rng) =>string;phone: (rng) =>string;ssn: (rng,block) =>string;uuid: (rng) =>string; }>
The synthetic-safety provider namespace. Every function draws only from a reserved range or the shipped fake-name pool - no value it returns can be real or plausibly-real PHI.
Example
import { createRng, safe } from "@cosyte/synth";
const rng = createRng(42);
safe.ssn(rng); // never-issued SSN
safe.phone(rng); // reserved 555-01NN number
SYNTH_FATAL_CODES
constSYNTH_FATAL_CODES:object
The stable fatal code registry. Additions-only thereafter.
Type Declaration
SYNTH_EMPTY_POOL
readonlySYNTH_EMPTY_POOL:"SYNTH_EMPTY_POOL"="SYNTH_EMPTY_POOL"
A value was drawn from an empty pool. Fatal - never a fabricated substitute.
SYNTH_INTENDED_WARNING_MISMATCH
readonlySYNTH_INTENDED_WARNING_MISMATCH:"SYNTH_INTENDED_WARNING_MISMATCH"="SYNTH_INTENDED_WARNING_MISMATCH"
A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended warning code(s). Fatal - never emit a mislabeled fixture.
SYNTH_INVALID_DECIMAL
readonlySYNTH_INVALID_DECIMAL:"SYNTH_INVALID_DECIMAL"="SYNTH_INVALID_DECIMAL"
A money value could not be read as an X12 decimal. Fatal - a generator never rounds to a float.
SYNTH_INVALID_PROFILE
readonlySYNTH_INVALID_PROFILE:"SYNTH_INVALID_PROFILE"="SYNTH_INVALID_PROFILE"
A defineSynthProfile spec was not usable (a missing or blank name). Fatal.
SYNTH_INVALID_RANGE
readonlySYNTH_INVALID_RANGE:"SYNTH_INVALID_RANGE"="SYNTH_INVALID_RANGE"
An integer range was requested with its maximum below its minimum. Fatal.
SYNTH_QUIRK_ANCHOR_ABSENT
readonlySYNTH_QUIRK_ANCHOR_ABSENT:"SYNTH_QUIRK_ANCHOR_ABSENT"="SYNTH_QUIRK_ANCHOR_ABSENT"
A quirk transform found no structural anchor to mutate, so the fixture would not carry the deviation it is labelled with. Fatal - a golden file that lies about its parser verdict is worse than no golden file.
SYNTH_UNMAPPED_CODE_SYSTEM
readonlySYNTH_UNMAPPED_CODE_SYSTEM:"SYNTH_UNMAPPED_CODE_SYSTEM"="SYNTH_UNMAPPED_CODE_SYSTEM"
A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal.
SYNTH_UNSUPPORTED_FORMAT
readonlySYNTH_UNSUPPORTED_FORMAT:"SYNTH_UNSUPPORTED_FORMAT"="SYNTH_UNSUPPORTED_FORMAT"
A format was requested that this build cannot generate through a real parser builder/serializer. Fatal - never a hand-written byte fallback.
No code path in this build raises it. All six formats generate, so it is reserved for a
future format that does not, and is kept because removing a published code is a breaking change.
An unsupported kind within a format that does generate is SYNTH_UNSUPPORTED_KIND.
SYNTH_UNSUPPORTED_KIND
readonlySYNTH_UNSUPPORTED_KIND:"SYNTH_UNSUPPORTED_KIND"="SYNTH_UNSUPPORTED_KIND"
A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal -
see resolveKind: a selector union is erased at run time, and a selector that falls through
either mislabels the fixture or hands the value to a peer builder that quotes it back.
SYNTH_UNSUPPORTED_QUIRK
readonlySYNTH_UNSUPPORTED_QUIRK:"SYNTH_UNSUPPORTED_QUIRK"="SYNTH_UNSUPPORTED_QUIRK"
A vendor quirk was requested that the target format's profile system does not support. Fatal - never a silent no-op and never a fabricated quirk.
Example
import { SYNTH_FATAL_CODES, SynthError } from "@cosyte/synth";
try {
// ...generate...
} catch (err) {
if (err instanceof SynthError && err.code === SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_FORMAT) {
// handle an unsupported format request
}
}
SYNTH_FATAL_MESSAGES
constSYNTH_FATAL_MESSAGES:Readonly<Record<SynthFatalCode,string>>
The frozen message registry - the only place a SynthError message can come from.
A message here is a fixed string. It never quotes the request that produced it, and there is no parameter through which it could: SynthError takes a code and nothing else. That is the whole mechanism, and it is deliberately a mechanism rather than a habit. Every one of these messages used to be assembled by interpolating the caller's value into a template, and the reason that was safe was not the design - it was that the caller happened to be passing a quirk name.
The trade is real and is accepted: a fatal no longer tells you which value it rejected. It tells
you which rule refused, on err.code, and the stack frame tells you where. The caller already
holds the value it passed.
Example
import { SYNTH_FATAL_CODES, SYNTH_FATAL_MESSAGES } from "@cosyte/synth";
SYNTH_FATAL_MESSAGES[SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL]; // => "A value was drawn from an empty pool."
SYNTHETIC_ASSIGNING_AUTHORITY
constSYNTHETIC_ASSIGNING_AUTHORITY:Readonly<{namespaceId:"COSYTE-SYNTH";universalId:"2.16.840.1.113883.19.999";universalIdType:"ISO"; }>
The synthetic assigning authority @cosyte/synth mints MRNs / account / member identifiers
under. There is no reserved MRN range (an MRN is unique only within its assigning-authority /
OID namespace), so - a documented design decision - every synthetic identifier
is scoped to a namespace that clearly cannot be a real facility's: a SYNTH-labelled authority whose
OID lives under HL7's designated example root 2.16.840.1.113883.19. A value under this AA can
never collide with a real record because the namespace itself is synthetic.
SYNTHETIC_CITY_NAMES
constSYNTHETIC_CITY_NAMES: readonlystring[]
Obviously-synthetic city names. Combined only ever with a synthetic street + a fake name (the combination is what identifies, and the combination is always synthetic).
SYNTHETIC_FAMILY_NAMES
constSYNTHETIC_FAMILY_NAMES: readonlystring[]
Obviously-synthetic family names. None is a plausible real surname at a real address.
SYNTHETIC_GIVEN_NAMES
constSYNTHETIC_GIVEN_NAMES: readonlystring[]
Obviously-synthetic given names. None is a plausible real person's name.
SYNTHETIC_STREET_NAMES
constSYNTHETIC_STREET_NAMES: readonlystring[]
Obviously-synthetic street names for structured address fields.
TEST_NET_V4_PREFIXES
constTEST_NET_V4_PREFIXES: readonlystring[]
RFC 5737 IPv4 documentation (TEST-NET) /24 network prefixes.
VERSION
constVERSION:string="0.0.6"
Library version string, equal to this package's published package.json#version.
Changesets owns the bump and rewrites package.json only, so the release version script runs
scripts/sync-version.mjs to rewrite this declaration in the same commit, and
test/sanity.test.ts compares the two so a skipped sync goes red instead of shipping a version
string that lies.
The : string annotation is deliberate: without it the declaration's inferred type is the string
literal itself, which bakes the current release into the emitted declarations and narrows every
consumer's type on each bump.
Example
import { VERSION } from "@cosyte/synth";
console.log(VERSION);
Functions
address()
address(
rng):SyntheticAddress
A synthetic address - a fake street + city, a reserved non-real ZIP (00000). A real state
abbreviation may appear (structural only) but is never combined with a real street + name + DOB.
Parameters
rng
The seeded generator.
Returns
Example
import { createRng, address } from "@cosyte/synth";
address(createRng(1)); // { street, city, state, zip: "00000" }
assertIntendedWarnings()
assertIntendedWarnings(
intendedWarnings,bareWarnings):void
Assert a freshly-generated quirk artifact actually round-trips to its intended warning(s), or
fail closed. This is the generator's self-check on the intended-warning contract: a
fixture whose bare parse does not produce exactly the declared code(s) is a mislabeled fixture - a
golden file that lies about the parser verdict it anchors - and must never be emitted. It is a
stronger guard than "the transform changed some bytes": a transform can mutate the wrong element (a
template a given document type does not key its warning on) and still change bytes while producing no
warning. Every format's generate*Quirk calls this after transforming, so the contract is enforced at
generation time, not merely at round-trip time.
It no longer takes the quirk name. That parameter existed for one reason - to be interpolated into the refusal - and a parameter whose only job is to reach a message is the exact shape this package is removing, so it is gone rather than merely unused. The refusal names neither code list either; both are caller-supplied, and the caller reads the comparison back off the arguments it holds.
Parameters
intendedWarnings
readonly string[]
The declared intended code(s).
bareWarnings
readonly string[]
The code(s) a bare parse of the generated artifact actually produced.
Returns
void
Throws
SynthError SYNTH_INTENDED_WARNING_MISMATCH when the bare parse did not produce exactly
the intended code(s).
Example
import { assertIntendedWarnings } from "@cosyte/synth";
assertIntendedWarnings(["UNKNOWN_SEGMENT"], ["UNKNOWN_SEGMENT"]); // ok
createRng()
createRng(
seed):Rng
Create a seeded, deterministic Rng. The same seed yields the same sequence everywhere.
Parameters
seed
number
The integer seed. Coerced to a 32-bit integer.
Returns
A fresh, independent Rng.
Example
import { createRng } from "@cosyte/synth";
const rng = createRng(12345);
rng.int(1, 6); // deterministic for seed 12345
dateYmd()
dateYmd(
rng,minYear?,maxYear?):string
A synthetic date in HL7 YYYYMMDD form, drawn uniformly within an inclusive year range. Comes
from the seeded generator (never wall-clock), so it is reproducible and implies no real event.
Parameters
rng
The seeded generator.
minYear?
number = 1930
Inclusive lower year bound (default 1930).
maxYear?
number = 2010
Inclusive upper year bound (default 2010).
Returns
string
An YYYYMMDD date string (always a valid calendar day).
Example
import { createRng, dateYmd } from "@cosyte/synth";
dateYmd(createRng(1), 1970, 2000); // "YYYYMMDD"
dea()
dea(
rng,person?):string
A synthetic DEA number - <registrant-type><initial> + 7 digits with a deliberately-invalid
checksum, so it can never be a validly-issued DEA registration (a real DEA number's 7th digit
satisfies the published DEA checksum). The first letter is a registrant-type letter, the
second is derived from person (its family initial) when supplied so the number reads plausibly; the
6-digit base is seeded and the check digit is set to (correct + 1) mod 10, guaranteeing the value
fails validation. NCPDP carries prescriber DEA, and this is the identity locus a refuter attacks
hardest, so - like npi - non-collision is a construction-level guarantee, not a heuristic.
Parameters
rng
The seeded generator.
person?
Optional name whose family initial becomes the DEA's second letter.
Returns
string
A DEA-shaped string that is provably not a real DEA registration.
Example
import { createRng, dea, isSyntheticDea } from "@cosyte/synth";
isSyntheticDea(dea(createRng(1))); // true - invalid checksum by construction
deaCheckDigit()
deaCheckDigit(
base6):number
The correct DEA check digit for a 7-digit numeric base. The published DEA checksum is
(d1 + d3 + d5) + 2·(d2 + d4 + d6), whose units digit is the 7th (check) digit. A real DEA
number satisfies this; a number whose 7th digit differs cannot be a validly-issued DEA registration.
Parameters
base6
string
The first 6 digits of the DEA number (positions 1–6).
Returns
number
The check digit (0–9) a real DEA number would carry for this base.
Example
import { deaCheckDigit } from "@cosyte/synth";
deaCheckDigit("123456"); // the units digit of (1+3+5) + 2·(2+4+6)
defineSynthProfile()
defineSynthProfile(
spec):SynthProfile
Define a reusable, frozen synthetic-fixture profile.
Parameters
spec
The profile spec; name is required and non-empty.
Returns
A deep-frozen SynthProfile.
Throws
SynthError SYNTH_INVALID_PROFILE when name is missing or blank.
Example
import { defineSynthProfile } from "@cosyte/synth";
const acme = defineSynthProfile({ name: "acme-hospital", quirks: [] });
email()
email(
rng,person?):string
A synthetic email at an RFC 2606 / 6761 reserved domain - <slug>@example.com.
Parameters
rng
The seeded generator.
person?
Optional name to derive the local-part slug from; otherwise a random slug is used.
Returns
string
A synthetic email address.
Example
import { createRng, email, name } from "@cosyte/synth";
email(createRng(1), name(createRng(1))); // "<given>.<family>@example.com"
identifier()
identifier(
rng,typeCode?):SyntheticIdentifier
A synthetic identifier (MRN / account / member id) scoped to the synthetic assigning authority.
There is no reserved MRN range, so non-collision is guaranteed by the namespace, not the value: the
identifier lives under a SYNTH authority no real facility uses.
Parameters
rng
The seeded generator.
typeCode?
"MR" | "AN" | "MB"
The HL7 identifier type: MR (default), AN, or MB.
Returns
Example
import { createRng, identifier } from "@cosyte/synth";
identifier(createRng(1), "MR"); // { value, typeCode: "MR", assigningAuthority: "COSYTE-SYNTH", ... }
ipv4()
ipv4(
rng):string
A synthetic IPv4 in an RFC 5737 TEST-NET block - never routable.
Parameters
rng
The seeded generator.
Returns
string
A TEST-NET IPv4 address string.
Example
import { createRng, ipv4 } from "@cosyte/synth";
ipv4(createRng(1)); // e.g. "192.0.2.NN"
ipv6()
ipv6(
rng):string
A synthetic IPv6 in the RFC 3849 documentation prefix 2001:db8::/32 - never routable.
Parameters
rng
The seeded generator.
Returns
string
A documentation-prefix IPv6 address string.
Example
import { createRng, ipv6 } from "@cosyte/synth";
ipv6(createRng(1)); // e.g. "2001:db8::NNNN"
isSyntheticDea()
isSyntheticDea(
value):boolean
Whether a DEA number (XX + 7 digits, case-insensitive) is provably synthetic - its check digit
(the 7th digit) does not match the published DEA checksum, so it cannot be a validly-issued DEA
registration. A checksum-valid DEA number (which could denote a real prescriber) returns false; a
value that is not the DEA shape returns false.
Parameters
value
string
The candidate DEA number (with or without incidental separators).
Returns
boolean
true when the DEA number's checksum is wrong (never a real DEA registration).
Example
import { isSyntheticDea } from "@cosyte/synth";
isSyntheticDea("AF1234561"); // depends on the base - true when the 7th digit is wrong
isSyntheticEmail()
isSyntheticEmail(
value):boolean
Whether an email's domain is an RFC 2606 / 6761 reserved / test domain.
Parameters
value
string
The candidate email address.
Returns
boolean
true when the domain is reserved (never real).
Example
import { isSyntheticEmail } from "@cosyte/synth";
isSyntheticEmail("faux.testerson@example.com"); // true
isSyntheticIp()
isSyntheticIp(
value):boolean
Whether an IP string is in an RFC 5737 (IPv4 TEST-NET) or RFC 3849 (IPv6 documentation) reserved
block. A real routable address returns false.
Parameters
value
string
The candidate IPv4 or IPv6 address.
Returns
boolean
true when the address is a reserved documentation address.
Example
import { isSyntheticIp } from "@cosyte/synth";
isSyntheticIp("192.0.2.44"); // true (TEST-NET-1)
isSyntheticIp("8.8.8.8"); // false (real)
isSyntheticNpi()
isSyntheticNpi(
value):boolean
Whether a 10-digit NPI is provably synthetic - i.e. its check digit is invalid, so it cannot be
a NPPES-issued NPI. A Luhn-valid 10-digit NPI (which could denote a real registered provider)
returns false; a non-10-digit value returns false (not an NPI shape).
Parameters
value
string
The candidate NPI (digits only, or with incidental separators).
Returns
boolean
true when the NPI's check digit is wrong (never a real NPI).
Example
import { isSyntheticNpi } from "@cosyte/synth";
isSyntheticNpi("1234567894"); // true - invalid check digit (valid would be 1234567893)
isSyntheticNpi("1234567893"); // false - Luhn-valid, could be a real NPI
isSyntheticPhone()
isSyntheticPhone(
value):boolean
Whether a phone string contains the NANP 555-0100…555-0199 reserved fictional line range.
Parameters
value
string
The candidate phone (any formatting).
Returns
boolean
true when the number is in the reserved fictional block.
Example
import { isSyntheticPhone } from "@cosyte/synth";
isSyntheticPhone("(202) 555-0142"); // true
isSyntheticSsn()
isSyntheticSsn(
value):boolean
Whether a ddd-dd-dddd (or ddddddddd) SSN string is drawn from an SSA never-issued / reserved
space - area 000, 666, or 900–999. A real, issuable SSN returns false.
Parameters
value
string
The candidate SSN (dashes optional).
Returns
boolean
true when the SSN is provably synthetic.
Example
import { isSyntheticSsn } from "@cosyte/synth";
isSyntheticSsn("900-12-3456"); // true (never issued)
isSyntheticSsn("123456789"); // false (issuable area 123)
luhnMod10()
luhnMod10(
digits):number
Internal
The Luhn sum (mod 10) of a numeric string, doubling every second digit from the right. Used to verify (or deliberately break) an NPI check digit.
Parameters
digits
string
A string of decimal digits.
Returns
number
The Luhn sum modulo 10 (0 ⇒ the string passes the Luhn check).
makeCorpus()
makeCorpus(
seed,artifacts,quirks?):Corpus
Assemble a deep-frozen Corpus from a seed and its artifacts, deriving the manifest.
Parameters
seed
number
The seed the artifacts were generated from.
artifacts
readonly Artifact[]
The generated artifacts, in order.
quirks?
readonly string[] = []
The quirk names applied (default none).
Returns
A deep-frozen, self-describing Corpus.
Example
import { makeCorpus } from "@cosyte/synth";
const corpus = makeCorpus(1, [{ format: "hl7v2", kind: "ADT^A01", content, warnings: [] }]);
corpus.manifest.counts["ADT^A01"]; // 1
name()
name(
rng):SyntheticName
A synthetic name drawn from the shipped clearly-fake pool.
Parameters
rng
The seeded generator.
Returns
Example
import { createRng, name } from "@cosyte/synth";
const { given, family } = name(createRng(1));
npi()
npi(
rng):string
A synthetic NPI - a 10-digit National Provider Identifier with a deliberately-invalid Luhn
check digit, so it can never be a NPPES-issued NPI (a real NPI must satisfy the 80840-prefixed
Luhn check). The 9-digit base is drawn from the seeded generator; the check digit is
set to (correct + 1) mod 10, guaranteeing the full value fails validation.
Parameters
rng
The seeded generator.
Returns
string
A 10-digit NPI-shaped string that is provably not a real NPI.
Example
import { createRng, npi, isSyntheticNpi } from "@cosyte/synth";
isSyntheticNpi(npi(createRng(1))); // true - invalid check digit by construction
npiCheckDigit()
npiCheckDigit(
base9):number
The correct NPI check digit for a 9-digit base - the value that makes 80840 + base + check pass
the Luhn check.
Parameters
base9
string
The 9-digit NPI base (positions 1–9).
Returns
number
The check digit (0–9) a real NPI would carry for this base.
Example
import { npiCheckDigit } from "@cosyte/synth";
npiCheckDigit("123456789"); // 3 - so 1234567893 is a Luhn-valid NPI shape
phone()
phone(
rng):string
A synthetic phone in the NANP reserved fictional block - (AAA) 555-01NN. The reserved
guarantee is the 555-01NN tail (exchange 555, line 0100–0199); the area code is any NANP-valid
NXX. Can never be a working number.
Parameters
rng
The seeded generator.
Returns
string
A formatted synthetic phone string.
Example
import { createRng, phone } from "@cosyte/synth";
phone(createRng(1)); // e.g. "(2XX) 555-01NN"
profileTolerated()
profileTolerated(
disposition,intendedWarnings,warningsUnderProfile):boolean
Evaluate whether a profiled parse tolerated a quirk as its disposition declares. Shared across the formats so the "suppressed vs re-badged" logic lives in exactly one place.
Parameters
disposition
The quirk's declared profile disposition.
intendedWarnings
readonly string[]
The bare-parse intended code(s).
warningsUnderProfile
readonly string[]
The code(s) the parser emitted with the profile active.
Returns
boolean
true iff the profile handled the quirk correctly for its disposition.
Example
import { profileTolerated } from "@cosyte/synth";
profileTolerated("suppressed", ["UNKNOWN_SEGMENT"], []); // true - the profile suppressed it
resolveKind()
resolveKind<
T>(allowed,requested):T
Resolve one caller-supplied selector against the closed set that governs it, or fail closed.
Type Parameters
T
T extends string
Parameters
allowed
readonly T[]
Every value the selector may take.
requested
string
The selector the caller supplied.
Returns
T
requested, narrowed to the union.
Throws
SynthError SYNTH_UNSUPPORTED_KIND when requested is not in allowed.
Example
import { resolveKind } from "@cosyte/synth";
resolveKind(["ccd", "referralNote"] as const, "ccd"); // "ccd"
resolveMix()
resolveMix<
T>(allowed,requested,fallback): readonlyT[]
Resolve every entry of a caller-supplied corpus mix, in order, or fail closed on the first unrecognised one.
It substitutes the default only when the caller supplied nothing, which is exactly what the
?? it replaced did. An empty array is a supplied mix and is returned as one. An earlier version
of this function also treated [] as "nothing supplied", on the stated grounds that it matched the
previous behaviour; it did not - ?? fires on undefined and never on [] - and it changed the
result of six published entry points, turning an explicit empty selection into "generate one of
everything". A convenience that fails open is not a convenience.
Type Parameters
T
T extends string
Parameters
allowed
readonly T[]
Every kind the corpus may generate.
requested
readonly string[] | undefined
The mix the caller supplied, or undefined for the default.
fallback
readonly T[]
The default mix, used only when requested is undefined.
Returns
readonly T[]
The resolved mix.
Throws
SynthError SYNTH_UNSUPPORTED_KIND on the first unrecognised entry.
Example
import { resolveMix } from "@cosyte/synth";
resolveMix(["Result", "Order"] as const, ["Order"], ["Result", "Order"]); // ["Order"]
resolveQuirk()
resolveQuirk(
registry,format,name):QuirkDescriptor
Resolve a requested quirk name against a format's registry, or fail closed. A quirk the format's
profile system does not support is a fatal SYNTH_UNSUPPORTED_QUIRK - never a silent no-op and never
a fabricated quirk with a made-up warning.
The refusal names neither the request nor the registry. registry, format and name are all
caller-supplied, and a diagnostic that quotes its input is a diagnostic that can be made to carry
anything the caller was holding - which for a fixture generator wired into someone else's pipeline
is not a hypothetical. Branch on err.code; the supported set is the registry you passed
(HL7_QUIRKS, CCDA_QUIRKS, ASTM_QUIRKS), which you can enumerate directly.
Parameters
registry
Readonly<Record<string, QuirkDescriptor>>
The format's quirk descriptors, keyed by name.
format
The format being generated.
name
string
The requested quirk name.
Returns
The matching QuirkDescriptor.
Throws
SynthError with code SYNTH_UNSUPPORTED_QUIRK when name is not a supported quirk.
Example
import { resolveQuirk } from "@cosyte/synth";
import { HL7_QUIRKS } from "@cosyte/synth/hl7";
resolveQuirk(HL7_QUIRKS, "hl7v2", "unknown-zsegment").intendedWarnings; // ["UNKNOWN_SEGMENT"]
sameCodeSet()
sameCodeSet(
a,b):boolean
Exact multiset (order-independent) equality of two code lists - the intended-warning comparison.
Parameters
a
readonly string[]
The first code list.
b
readonly string[]
The second code list.
Returns
boolean
true iff the two lists contain the same codes with the same multiplicities.
Example
import { sameCodeSet } from "@cosyte/synth";
sameCodeSet(["A", "B"], ["B", "A"]); // true
sfc32Next()
sfc32Next(
s):number
Advance an Sfc32State in place by one step and return the next unsigned 32-bit integer.
This is the canonical sfc32 step. The state object is mutated (the counter d increments and the
mixing words rotate); callers that need reproducible independence hold their own state and never
share it - ../rng/rng.Rng creates a fresh state per seed so two runs from the same seed are
identical.
Parameters
s
The state to advance. Mutated in place.
Returns
number
The next uint32 in the stream.
Example
import { sfc32Next, type Sfc32State } from "@cosyte/synth";
const s: Sfc32State = { a: 1, b: 2, c: 3, d: 4 };
const x = sfc32Next(s); // uint32
splitmix32()
splitmix32(
seed): () =>number
A stateful splitmix32 step function. Each call advances the internal 32-bit state and returns the
next unsigned 32-bit integer. Deterministic for a given seed.
Parameters
seed
number
The 32-bit seed. Coerced to a 32-bit integer via | 0.
Returns
A nullary function returning the next uint32 in the stream.
() => number
Example
import { splitmix32 } from "@cosyte/synth";
const next = splitmix32(12345);
const a = next(); // deterministic uint32
ssn()
ssn(
rng,block?):string
A synthetic SSN - dashed AAA-GG-SSSS. Default draws the SSA never-issued area space
(900–999); block: "advertising" draws SSA's reserved advertising block (987-65-4320…4329).
A value from this function can never be a real SSN.
Parameters
rng
The seeded generator.
block?
SsnBlock = "never-issued"
Which reserved space to draw from. Defaults to "never-issued".
Returns
string
A dashed synthetic SSN string.
Example
import { createRng, ssn } from "@cosyte/synth";
ssn(createRng(1)); // e.g. a 900-area, never-issued SSN
uuid()
uuid(
rng):string
A deterministic UUIDv4-shaped surrogate key from the seeded generator. Because it is seeded (not
from node:crypto, which is not reproducible), the cryptographic non-collision argument is weaker -
acceptable because the identifier namespace is synthetic anyway, and noted honestly.
Parameters
rng
The seeded generator.
Returns
string
A canonical 8-4-4-4-12 lowercase-hex UUID string with version 4 and RFC 4122 variant.
Example
import { createRng, uuid } from "@cosyte/synth";
uuid(createRng(1)); // "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
validateProfileQuirks()
validateProfileQuirks(
profile,registry,format): readonlystring[]
Validate the quirk names carried by a SynthProfile against a format's registry, failing closed
on the first unsupported one. Lets a consumer author a fixture recipe with defineSynthProfile and
have its quirks checked against the parser's real tolerance before any fixture is generated.
Parameters
profile
The synth profile whose quirks to validate.
registry
Readonly<Record<string, QuirkDescriptor>>
The format's quirk descriptors.
format
The format being generated.
Returns
readonly string[]
The validated quirk names (the profile's, in order).
Throws
SynthError SYNTH_UNSUPPORTED_QUIRK for the first unsupported quirk.
Example
import { validateProfileQuirks, defineSynthProfile } from "@cosyte/synth";
import { HL7_QUIRKS } from "@cosyte/synth/hl7";
const p = defineSynthProfile({ name: "site", quirks: ["unknown-zsegment"] });
validateProfileQuirks(p, HL7_QUIRKS, "hl7v2"); // ["unknown-zsegment"]