Skip to content

The generators ​

Everything Phony produces comes from a generator. There are five core generators — each produces a value directly — plus composition, which builds new generators out of the others. Pick a core generator by the nature of the data you need; reach for composition when a value is made of other values.

NeedGeneratorExample
Computed from an algorithmLogicUUID, random int, date, sequence
One of a fixed valid setListcountry, HTTP status, currency, coherent record
Realistic for a localeModelperson names, company names
Matching a distributionStatisticalages ~ Normal(35, 12), correlated columns
Chronological datesEvent Sequencecreated → paid → shipped
Made of other valuesCompositionemail, full address, SKU

The mental model, one line each — reach for:

  • Logic when the value is computed, not chosen: an id, a number in a range, a date, a running sequence. No data file, no locale, the fastest path.
  • List when the value must be one of a known-correct set — a real currency, a valid HTTP status, or (over an asset of objects) a whole coherent record so the fields can't disagree.
  • Model when the value should look real for a locale without being copied from real data — names, usernames, product names, prose. It's trained, novel, and locale-flavoured.
  • Statistical when the shape matters — ages that cluster around a mean, categories in real proportions, columns that move together.
  • Event Sequence when you need ordered dates — a created → paid → shipped timeline where each step follows the last by a plausible delay.
  • Composition when the value is made of other values, or when you want to ship a reusable generator as data. It's the glue and the extension point.

Logic — pure algorithm ​

No data, no locale, fastest. uuid_v4/uuid_v7/ulid/nanoid, int_between, float_between, boolean, datetime_between/date_between/timestamp, sequence, gaussian, exponential.

json
"age": { "type": "logic", "algorithm": "int_between", "params": { "min": 18, "max": 85 } }

List — a finite valid set ​

When the value must be correct (a real currency, a valid status). Inline or from a locale asset; optionally weighted.

json
"status": { "type": "list", "values": [
  { "value": "active", "weight": 70 }, { "value": "churned", "weight": 30 } ] }

A list over an asset of objects returns one whole record, so its fields always agree — this is how you get a coherent city + country + postal without ever producing "Ankara, Japan":

json
"location": { "type": "list", "source": "geo.locations" }

Model — realistic, locale-flavoured (N-gram) ​

Trained on real samples, it generates new values that follow the same patterns — Turkish names that sound Turkish, without copying the training set. Modes: word, sentence, text, paragraph, poem, acrostic, real_word.

json
"first_name": { "type": "model", "source": "person.first_names",
                "generation": { "mode": "word" } }

Statistical — distributions ​

Match real-world shape, not just randomness: categorical (frequencies), continuous (normal, lognormal, exponential, uniform, poisson, beta, gamma), or multivariate (correlated columns drawn together), with optional differential privacy.

json
"customer_age": { "type": "statistical", "mode": "continuous", "distribution": "normal",
                  "params": { "mean": 35, "stddev": 12 }, "constraints": { "min": 18, "max": 85 } }

Event Sequence — chronological dates ​

A base event plus probabilistic, delayed follow-ups that respect order.

json
"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 } ] }

Composition — generators made of generators ​

The universal composer: a generator authored as data (PGDL + PEL), made of other generators, nested without limit. Once registered it's usable exactly like a built-in — so a community can ship generators without writing Rust.

A composition has input params, named let steps (each a generator or a computed PEL expression, evaluated in dependency order), and produces its value in one of three shapes:

  • body — a {{ … }} template → a string (email, SKU, formatted text)
  • variants — a weighted choice among body templates
  • output — an object of computed, mutually-consistent fields

Coherence is a property of a let: name a value once, reference it many times, and every reference reads that one bound value.

json
{ "name": "@acme:email",
  "let": { "first": { "type": "model", "source": "person.first_names", "generation": { "mode": "word" } } },
  "body": "{{ lowercase(first) }}@example.com" }
json
{ "name": "@acme:financials",
  "let": {
    "salary": { "type": "logic", "algorithm": "int_between", "params": { "min": 30000, "max": 250000 } },
    "bonus":  { "computed": "round(salary * 0.15, 2)" },
    "net":    { "computed": "round(salary + bonus, 2)" } },
  "output": { "salary": "{{ salary }}", "bonus": "{{ bonus }}", "net": "{{ net }}" } }

The repeat step — N items per parent ​

A let step can also be a repeat: it invokes a sub-generator several times and collects the results into an array. This is the "N rows per parent" primitive (Faker's ->count(n)) — 3–5 line items for this order, a handful of tags per post:

json
{ "name": "@shop:order",
  "let": { "lines": { "repeat": {
    "count": "1-4",
    "of": { "type": "logic", "algorithm": "int_between", "params": { "min": 1, "max": 9 } } } } },
  "output": { "customer": "acme", "lines": "{{ lines }}" } }

count is a literal, an inclusive "a-b" range drawn per invocation, or a PEL expression over the locals; each item draws its own seed (salted by the step name and item ordinal), so the array is reproducible yet its items vary. Add "unique": true to de-duplicate. Run over four invocations you get arrays of different lengths — [5,2,5], [6,1], [9,6,4,8], [7,8,9,6] — each stable for its seed. Because of may itself be a composition, repeat nests.

Folded in

Earlier drafts listed template and linked as separate generator types. Both are now composition: a template is a composition with a body (and optional weighted variants); a linked record is either a list over an asset (pick one coherent record) or a composition output (compute coherent fields over prior steps). The old operations pipeline is just PEL function calls in the body.


For exact parameters and edge cases, see the Generator Types spec and the Generator × Asset matrix.

Phony Cloud — Documentation & Specification