What is PGDL?
PGDL (Phony Generator Definition Language) is the declarative, JSON-based language for defining generators — pure producers of values. You don't write how to produce a value step by step; you declare what it is (an id; a name that sounds Turkish; an email built from that name) and Phony produces it, reproducibly, from a seed.
- Declarative — describe the value, not the procedure.
- JSON — editable by hand, by tools, and by agents; diffable in Git.
- Deterministic — the definition, a seed, an invocation index, and the reference instant fully determine the output (see How generation works).
A library language, not a dataset spec
The sharpest way to place PGDL: it's a language for building a library of generators, not a language for describing a dataset. A dataset-spec language would talk about tables, how many rows each has, which column is a foreign key into which other, and how two tables stay consistent. PGDL says none of that. It only ever answers "what is one value, and how is it produced?" — reproducibly, from a seed.
That single-minded scope is a deliberate design choice, not a gap. Because a generator never mentions a table or a sibling column, it stays pure and portable: the same @acme/person:full_name behaves identically whether it's invoked by the CLI, an OSS library, or the cloud product binding it to a live database's schema. The moment a generator could reach a table, it would stop being reusable. Wiring generators into tables and relationships is a different layer — the cloud product (see What PGDL is not).
A generator is just a generator
A generator knows nothing about tables, columns, rows, or relationships. It takes a seed (plus a few ambient inputs) and produces one value. That value can be a single scalar, or a structured object or array — even nested — and its keys may be named or not. How many values you want is a separate concern (see Generation); arranging values into tables is a different layer entirely — the cloud product (see What PGDL is not).
The shape of a generator
Every generator — built-in or custom — is one uniform envelope:
{ "use": "@phony/core:logic.int_between",
"params": { "min": 18, "max": 80 },
"constraints": { },
"modifiers": { "unique": true, "nullable": 0.05, "transform": "<PEL>" } }In PGDL you usually write the friendly per-type shape, which compiles to that envelope:
{ "type": "logic", "algorithm": "int_between", "params": { "min": 18, "max": 80 } }See Generators in depth for the envelope, the universal modifiers, and every generator type.
Generators are made of generators
A generator can be built from other generators — and those from others, nested without limit. The pure-data way is a composition: { name, params, let, body }, where each let step runs a generator (or computes a PEL expression) and body assembles the result.
{
"name": "@acme/person:full_name",
"let": {
"first": { "type": "model", "source": "person.first_names", "generation": { "mode": "word" } },
"last": { "type": "model", "source": "person.last_names", "generation": { "mode": "word" } }
},
"body": "{{ capitalize(first) }} {{ capitalize(last) }}"
}References inside a generator resolve against its own let/param values — never a table or a sibling field. To reuse one value, name it with a let and reference that name; the same name yields the same value (coherence is a property of the let). A composition is usable exactly like a built-in once registered, so generators ship in packages — no Rust required.
Generation: the invocation side
A generator's definition is one thing; generation — calling it — is another. Invoking a generator is like sampling an n-gram model: you ask for one value, or for N. Each invocation gets its own derived seed and an ordinal index (what sequence/cycle key off); the unique and nullable modifiers apply across a run of invocations.
let value = generate_one(&def, ®istry, seed, 0)?; // one value
let values = generate(&def, ®istry, seed, 1000)?; // a thousandSee The engine (Rust) for the runnable API.
What PGDL is not — and never will be
PGDL defines generators, full stop. Tables, foreign keys, relationships between tables, row-level consistency, "how many rows of each entity," and which data a generator transforms (entities, relationships, scenarios, sync rules) are not PGDL — not "not yet," but a different layer: the Phony cloud / SaaS product, which decides how generators are wired together and applied to real data. Keeping PGDL to only generator definition is what lets a generator stay pure and portable — the cloud then binds the same generators to an authored schema or to one introspected from a live database. See the cloud platform spec for that layer.
How to read the rest of these docs
| You want… | Read |
|---|---|
| The envelope, modifiers, every generator type | Generators in depth |
| Real input → real output for every feature | Generator gallery |
The {{ … }} expression language and its functions | Expressions (PEL) |
| Composition, seeds, determinism, invoking a generator | How generation works |
| Install & call a generator (Rust) | The engine (Rust) |
The exhaustive design specification lives under Spec → PGDL Specification; the table/relationship/sync layer is in the Cloud platform spec.