Generators in depth
A generator is a named producer of values. Every generator — built-in or third-party — obeys one uniform envelope; the friendly per-type PDL syntax compiles down to it.
The envelope
Internally, every generator invocation is:
{
"use": "@phony/core:logic.int_between",
"params": { "min": 18, "max": 80 },
"constraints": { },
"modifiers": { "unique": true, "nullable": 0.05, "transform": "<PEL>" }
}| Key | Meaning |
|---|---|
use | namespaced generator reference (built-ins are @phony/core:*) |
params | generator-specific inputs, validated against the generator's manifest |
constraints | generator-aware output filters (length, prefix, min/max) |
modifiers | cross-cutting, applied to any generator (see below) |
In PDL you write the per-type shape ({ "type": "logic", "algorithm": "int_between", "params": {…} }) and the compiler maps type(+algorithm) → use, gathers the right keys into params, and lifts unique/nullable/transform into modifiers.
Modifiers (work on every generator)
| Modifier | Type | Effect |
|---|---|---|
unique | bool | values are de-duplicated across rows of the entity |
nullable | number 0..1 | probability the value is null instead |
transform | string (PEL) | a PEL expression applied to the produced value, bound as value |
"score": { "type": "logic", "algorithm": "int_between", "params": { "min": 0, "max": 100 },
"transform": "if(value >= 50, 'pass', 'fail')" }Tiers
Built-ins are the Tier 0 primitive kernel (native, fast). Userland generators are Tier 1 (pure-data compositions = primitives + PEL — the 95% case) or Tier 2 (WASM, deferred). The envelope is the stable ABI that lets any of them plug in.
Custom generators (use)
The seven types above are the built-ins. Because the registry is an open set, a PDL schema can also reference any generator registered under its namespaced name with use instead of type:
"iban": { "use": "@acme/finance:iban", "params": { "country": "TR" } }params (plus constraints/unique/nullable/transform) work exactly as for a built-in — the generator validates params against its own manifest. Once declared, a custom generator is usable everywhere a built-in is: as a named generator, inside a {{template}}, or inline on a field.
"generators": {
"iban": { "use": "@acme/finance:iban", "params": { "country": "TR" } },
"label": { "type": "template", "pattern": "IBAN: {{iban}}" }
}The generator must be registered in the runtime first (registry.register(...) in Rust today; package install later). An unknown use reference is an error at generation time.
1. Logic — pure algorithm
No data, no locale, fully reproducible. use = @phony/core:logic.<algorithm>.
| algorithm | params | output |
|---|---|---|
uuid_v4 | — | random UUID |
uuid_v7 | — | time-sortable UUID (synthetic timestamp from the seed) |
ulid | — | Crockford-base32 ULID |
nanoid | length (default 21) | URL-safe id |
int_between | min, max | integer in [min, max] |
float_between | min, max, precision | float |
boolean | probability (default 0.5) | boolean |
datetime_between | start, end | ISO-8601 datetime |
date_between | start, end | ISO-8601 date |
time_between | start, end (HH:MM:SS) | time-of-day |
timestamp | start, end | Unix seconds (integer) |
sequence | start, step | start + row*step |
gaussian | mean, stddev | normal-distributed float |
exponential | lambda | exponential-distributed float |
start/end accept ISO-8601 or relative expressions (-1year, now, now+7days, today-30days) resolved against the reference instant.
"created_at": { "type": "logic", "algorithm": "datetime_between",
"params": { "start": "-2years", "end": "now" } }2. List — finite valid set
Pick from a known-correct set. Data is inline or from a locale asset. Elements may be scalars, { value, weight } (weighted), or metadata objects (returned whole).
"status": { "type": "list", "source": "inline", "values": [
{ "value": "active", "weight": 70 }, { "value": "churned", "weight": 30 } ] },
"country": { "type": "list", "source": "geo.countries", "locale_independent": true }| key | meaning |
|---|---|
source | "inline", an asset name, or { "asset": "name" } |
values | inline elements (when source is inline) |
locale_independent | resolve the asset the same in every locale |
Object elements expose nested fields to templates: {{country.code}}.
3. Model — N-gram (locale-flavoured)
Generates novel values from a trained model (the Model generator). Requires a generation block.
"first_name": {
"type": "model",
"source": "person.first_names",
"generation": { "mode": "word", "params": { "starts_with": "A" } },
"constraints": { "min_length": 3, "max_length": 12 }
}| key | values |
|---|---|
source | the model asset name (a .ngram) |
generation.mode | word · sentence · text · paragraph · poem · acrostic · real_word |
generation.params | mode options: word_count, punctuation, max_chars, suffix, sentence_count, starts_with, … |
constraints | min_length · max_length · starts_with · ends_with · contains · exclude_originals |
params shapes the generation; constraints filter the output.
4. Template — composition
Compose other values via the expression language.
"email": { "type": "template", "pattern": "{{lowercase(first_name)}}@{{pick(['gmail.com','acme.com'])}}" },
"street": { "type": "template", "variants": [
{ "pattern": "{{first_name}} Sokak", "weight": 35 },
{ "pattern": "{{number:1-2000}}. Sokak", "weight": 65 } ] },
"slug": { "type": "template", "pattern": "{{product_name}}", "operations": ["slugify"] }| key | meaning |
|---|---|
pattern | a single template string |
variants | weighted alternatives [{ pattern, weight }] (instead of pattern) |
operations | a post-pipeline of PEL functions applied to the result |
5. Statistical — distributions
Match real-world shape.
"order_status": { "type": "statistical", "mode": "categorical", "values": [
{ "value": "completed", "weight": 70 }, { "value": "pending", "weight": 30 } ] },
"age": { "type": "statistical", "mode": "continuous", "distribution": "normal",
"params": { "mean": 35, "stddev": 12 }, "constraints": { "min": 18, "max": 85 },
"differential_privacy": { "enabled": true, "epsilon": 1.0, "mechanism": "laplace" } }| key | values |
|---|---|
mode | categorical (weighted set) · continuous (a distribution) |
distribution | normal lognormal exponential uniform poisson beta gamma |
params | distribution params (mean/stddev, mu/sigma, lambda, alpha/beta, shape/scale) |
constraints | min/max clamp (resampled) |
differential_privacy | { enabled, epsilon, mechanism: laplace|gaussian } |
Algebraic/multivariate relationships are expressed with computed fields.
6. Linked — coherent multi-column
One record drives several columns, so combinations are always valid.
"location": { "type": "linked", "source": "geo.locations",
"columns": ["city","country","postal_code"] },
"financials": { "type": "linked", "rules": {
"salary": { "type": "statistical", "mode": "continuous", "distribution": "lognormal",
"params": { "mu": 10, "sigma": 0.5 } },
"bonus": "salary * uniform(0.05, 0.20)",
"net": "salary + bonus" } }| form | meaning |
|---|---|
source + columns | pick one record from a linked asset; expose its columns |
rules | compute named columns, each a generator definition or a PEL expression over prior columns |
Fields reference its columns: location.city, financials.net. All columns of one linked generator in a row come from the same draw (coherence).
7. Event Sequence — chronological dates
A base event plus probabilistic, delayed follow-ups.
"timeline": { "type": "event_sequence", "events": [
{ "name": "created_at", "base": true, "range": { "start": "-1year", "end": "now" } },
{ "name": "paid_at", "after": "created_at", "delay": { "min": "0h", "max": "24h" }, "probability": 0.95 },
{ "name": "shipped_at","after": "paid_at", "delay": { "min": "1d", "max": "3d" }, "probability": 0.90 } ] }| event key | meaning |
|---|---|
base | the anchor event; range: { start, end } |
after | occurs after the named event |
delay | { min, max } durations (s/m/h/d/w) |
probability | chance the event occurs (else null, breaking the chain) |
The generator returns an object of event → ISO datetime | null; fields reference timeline.created_at, etc.
For exact parameter validation and design rationale, see Spec → Generator Types and the Generator × Asset matrix.