Skip to content

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:

json
{
  "use":         "@phony/core:logic.int_between",
  "params":      { "min": 18, "max": 80 },
  "constraints": { },
  "modifiers":   { "unique": true, "nullable": 0.05, "transform": "<PEL>" }
}
KeyMeaning
usenamespaced generator reference (built-ins are @phony/core:*)
paramsgenerator-specific inputs, validated against the generator's manifest
constraintsgenerator-aware output filters (length, prefix, min/max)
modifierscross-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)

ModifierTypeEffect
uniqueboolvalues are de-duplicated across rows of the entity
nullablenumber 0..1probability the value is null instead
transformstring (PEL)a PEL expression applied to the produced value, bound as value
json
"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:

json
"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.

json
"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>.

algorithmparamsoutput
uuid_v4random UUID
uuid_v7time-sortable UUID (synthetic timestamp from the seed)
ulidCrockford-base32 ULID
nanoidlength (default 21)URL-safe id
int_betweenmin, maxinteger in [min, max]
float_betweenmin, max, precisionfloat
booleanprobability (default 0.5)boolean
datetime_betweenstart, endISO-8601 datetime
date_betweenstart, endISO-8601 date
time_betweenstart, end (HH:MM:SS)time-of-day
timestampstart, endUnix seconds (integer)
sequencestart, stepstart + row*step
gaussianmean, stddevnormal-distributed float
exponentiallambdaexponential-distributed float

start/end accept ISO-8601 or relative expressions (-1year, now, now+7days, today-30days) resolved against the reference instant.

json
"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).

json
"status": { "type": "list", "source": "inline", "values": [
  { "value": "active", "weight": 70 }, { "value": "churned", "weight": 30 } ] },
"country": { "type": "list", "source": "geo.countries", "locale_independent": true }
keymeaning
source"inline", an asset name, or { "asset": "name" }
valuesinline elements (when source is inline)
locale_independentresolve 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.

json
"first_name": {
  "type": "model",
  "source": "person.first_names",
  "generation": { "mode": "word", "params": { "starts_with": "A" } },
  "constraints": { "min_length": 3, "max_length": 12 }
}
keyvalues
sourcethe model asset name (a .ngram)
generation.modeword · sentence · text · paragraph · poem · acrostic · real_word
generation.paramsmode options: word_count, punctuation, max_chars, suffix, sentence_count, starts_with, …
constraintsmin_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.

json
"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"] }
keymeaning
patterna single template string
variantsweighted alternatives [{ pattern, weight }] (instead of pattern)
operationsa post-pipeline of PEL functions applied to the result

5. Statistical — distributions

Match real-world shape.

json
"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" } }
keyvalues
modecategorical (weighted set) · continuous (a distribution)
distributionnormal lognormal exponential uniform poisson beta gamma
paramsdistribution params (mean/stddev, mu/sigma, lambda, alpha/beta, shape/scale)
constraintsmin/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.

json
"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" } }
formmeaning
source + columnspick one record from a linked asset; expose its columns
rulescompute 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.

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 },
  { "name": "shipped_at","after": "paid_at",    "delay": { "min": "1d", "max": "3d" }, "probability": 0.90 } ] }
event keymeaning
basethe anchor event; range: { start, end }
afteroccurs after the named event
delay{ min, max } durations (s/m/h/d/w)
probabilitychance 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.

Phony Cloud — Documentation & Specification