Skip to content

Entities & relationships

An entity is a table: a named set of fields, each producing one value per row. Entities turn generators into rows you can ship.

json
"entities": {
  "User": {
    "table": "users",
    "fields": {
      "id":    { "generator": "uuid", "primary_key": true },
      "email": { "generator": "email", "unique": true }
    }
  }
}

table is the optional physical table name (defaults to the entity name).

Field kinds

A field is exactly one of these four shapes:

ShapeMeaning
{ "generator": "name" }run a named schema generator (dotted location.city allowed)
{ "type": …, … }an inline generator definition (not reused elsewhere)
{ "computed": "expr" }a PEL expression over sibling fields
{ "ref": "Entity.field" }a foreign key

Plus optional flags on any field:

FlagEffect
primary_keymarks the row's key (informational; also the FK target)
uniquede-duplicate this field across rows
nullablethe field may be null
json
"fields": {
  "id":         { "generator": "uuid", "primary_key": true },
  "first_name": { "generator": "first_name" },
  "is_active":  { "type": "logic", "algorithm": "boolean", "params": { "probability": 0.9 } },
  "full_name":  { "type": "template", "pattern": "{{self.first_name}} {{self.last_name}}" },
  "user_id":    { "ref": "User.id" }
}

References within a row

A template or computed field can reference other fields/generators:

  • {{name}} — run the named generator (or, if a field of that name exists in the row, reuse its value).
  • {{self.field}} — read a sibling field of the same row.
  • {{obj.prop}} — a nested property of an object value.

References are memoized per row: the same reference resolves to one value, so email and full_name see the same first_name. The row is internally consistent.

Computed fields

computed evaluates a PEL expression over sibling fields — arithmetic, formatting, conditionals:

json
"unit_price": { "generator": "price" },
"quantity":   { "generator": "qty" },
"subtotal":   { "computed": "quantity * unit_price" },
"tax":        { "computed": "round(subtotal * 0.18, 2)" },
"total":      { "computed": "subtotal + tax" }

Fields are evaluated in topological order of their dependencies, so subtotal exists before tax reads it. A dependency cycle is an error.

Foreign keys & relationships

ref makes a field point at another entity's field. Phony generates parent entities before children and picks a real parent key — joins are always valid.

json
"entities": {
  "User":  { "fields": { "id": { "generator": "uuid", "primary_key": true } } },
  "Order": { "fields": {
    "id":      { "generator": "uuid", "primary_key": true },
    "user_id": { "ref": "User.id" }
  }}
}

Self-references work (e.g. Category.parent_idCategory.id); make them nullable so the first rows can have no parent:

json
"parent_id": { "ref": "Category.id", "nullable": true }

You can declare relationships explicitly for cardinality and delete semantics (used by the cloud DB-sync layer; generation only needs the ref):

json
"relationships": [
  { "from": "Order.user_id", "to": "User.id", "cardinality": "many-to-one", "on_delete": "cascade" }
]

Scenarios

A scenario names how many rows of each entity to generate. You can have several (dev, staging, load_test):

json
"scenarios": {
  "dev":     { "User": 100, "Order": 300 },
  "staging": { "User": 10000, "Order": 50000 }
}

A count may be a plain integer or { "count": N }. Entities are generated in foreign-key order so parents exist before the children that reference them.

What you get back

Generating a scenario yields tables — for each entity, an array of row objects:

json
{
  "User":  [ { "id": "018f…", "first_name": "Mehmet", "email": "mehmet.yilmaz@example.com",  },  ],
  "Order": [ { "id": "018f…", "user_id": "018f…", "total": 245.90 },  ]
}

See How generation works for the row-evaluation order, seeds, and how to run a schema.

Phony Cloud — Documentation & Specification