Skip to content

PDL (Phony Definition Language) Specification

PDL is a declarative JSON-based language for defining data generation schemas. This document provides the complete language specification.

Format Decision: PDL uses JSON as the canonical format with JSON Schema for IDE autocomplete and validation. String templates ({{expression}}) provide readable composition. CLI validation catches template reference errors at build time.

JSON Schema

All PDL schemas should reference the JSON Schema for IDE support:

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "name": "My Schema"
}

The JSON Schema provides:

  • Autocomplete: Property suggestions as you type
  • Validation: Real-time error highlighting for structure errors
  • Documentation: Hover tooltips with property descriptions

Note: JSON Schema validates structure but not template content ({{...}}). Template references are validated by the CLI at build time with clear error messages.

Schema Structure

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "locale": "tr_TR",
  "name": "Schema Name",
  "description": "Optional description",

  "metadata": {
    "author": "Author Name",
    "license": "MIT",
    "tags": ["ecommerce", "turkish"]
  },

  "inherits": "base",

  "generators": {
    "generator_name": {
      "type": "logic | list | model | template"
    }
  },

  "entities": {
    "EntityName": {
      "fields": {
        "field_name": {
          "generator": "generator_name"
        }
      }
    }
  },

  "relationships": [
    {
      "from": "EntityA.field",
      "to": "EntityB.field",
      "cardinality": "one-to-one | one-to-many | many-to-one | many-to-many"
    }
  ],

  "scenarios": {
    "scenario_name": {
      "EntityName": 100
    }
  }
}

Version Declaration

json
{
  "version": "1.0"
}
VersionStatusFeatures
1.0CurrentFull PDL support

Locale Declaration

json
{
  "locale": "tr_TR"
}

The locale affects:

  • Which model files are loaded (from locale-specific paths)
  • Which list files are loaded
  • Default date/number formatting

Supported locales follow the language_COUNTRY format:

  • tr_TR - Turkish (Turkey)
  • en_US - English (United States)
  • en_GB - English (United Kingdom)
  • de_DE - German (Germany)
  • fr_FR - French (France)
  • etc.

Package Inheritance

json
{
  "inherits": "base"
}

Or with scoped packages:

json
{
  "inherits": "@phony/ecommerce-base"
}

Inheritance allows packages to extend other packages:

┌─────────────────────────────────────────────────────────────────────────┐
│           INHERITANCE RESOLUTION ORDER                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  my-package (most specific)                                             │
│       │                                                                  │
│       └─▶ inherits: tr_TR                                               │
│               │                                                          │
│               └─▶ inherits: base (least specific)                       │
│                                                                          │
│  Resolution:                                                             │
│  1. Look in my-package                                                  │
│  2. If not found, look in tr_TR                                         │
│  3. If not found, look in base                                          │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Generator Definitions

Logic Generator

json
{
  "generators": {
    "user_id": {
      "type": "logic",
      "algorithm": "uuid_v7"
    },
    "age": {
      "type": "logic",
      "algorithm": "int_between",
      "params": {
        "min": 18,
        "max": 85
      }
    },
    "price": {
      "type": "logic",
      "algorithm": "float_between",
      "params": {
        "min": 0.01,
        "max": 9999.99,
        "precision": 2
      },
      "nullable": false,
      "description": "Product price in local currency"
    }
  }
}

Available algorithms:

AlgorithmParametersOutput
uuid_v4-UUID v4 string
uuid_v7-UUID v7 string
ulid-ULID string
nanoidlength (default: 21)Nano ID string
int_betweenmin, maxInteger
float_betweenmin, max, precisionFloat
booleanprobability (default: 0.5)Boolean
datetime_betweenstart, end, formatDateTime string
date_betweenstart, end, formatDate string
time_betweenstart, end, formatTime string
timestampstart, endUnix timestamp
sequencestart, stepIncrementing integer
gaussianmean, stddevFloat (normal dist.)
exponentiallambdaFloat (exp. dist.)

List Generator

json
{
  "generators": {
    "city": {
      "type": "list",
      "source": "lists/geo/cities.json"
    },
    "status": {
      "type": "list",
      "source": "inline",
      "values": ["active", "pending", "cancelled"]
    },
    "order_status": {
      "type": "list",
      "source": "inline",
      "values": [
        { "value": "completed", "weight": 60 },
        { "value": "pending", "weight": 25 },
        { "value": "cancelled", "weight": 10 },
        { "value": "refunded", "weight": 5 }
      ]
    },
    "http_method": {
      "type": "list",
      "source": "inline",
      "values": ["GET", "POST", "PUT", "DELETE"],
      "locale_independent": true
    },
    "country": {
      "type": "list",
      "source": "lists/geo/countries.json",
      "locale_independent": true,
      "nullable": false,
      "description": "ISO 3166 country"
    }
  }
}

Model Generator

Model generators require a generation block specifying the output mode. See N-gram Models for complete details.

json
{
  "generators": {
    "first_name": {
      "type": "model",
      "source": "models/person_names.ngram",
      "generation": { "mode": "word" }
    },
    "username": {
      "type": "model",
      "source": "models/usernames.ngram",
      "generation": { "mode": "word" },
      "constraints": {
        "min_length": 4,
        "max_length": 16
      }
    },
    "tagline": {
      "type": "model",
      "source": "models/slogans.ngram",
      "generation": {
        "mode": "sentence",
        "params": {
          "word_count": "{{number:3-8}}",
          "punctuation": [".", "!"]
        }
      }
    },
    "company_name": {
      "type": "model",
      "source": "models/companies.ngram",
      "generation": {
        "mode": "word",
        "params": { "starts_with": "A" }
      },
      "constraints": {
        "min_length": 3,
        "max_length": 50
      },
      "nullable": false,
      "description": "Turkish company name starting with A"
    }
  }
}

Generation modes: word, sentence, text, paragraph, poem, acrostic, real_word. The generation block is required - there is no default mode.

Note: params controls generation behavior (e.g., starts_with), while constraints validates output (e.g., min_length - rejects if not met).

Template Generator

json
{
  "generators": {
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@{{pick(domains)}}"
    },
    "street_name": {
      "type": "template",
      "variants": [
        { "pattern": "{{first_name}} Sokak", "weight": 35 },
        { "pattern": "{{last_name}} Caddesi", "weight": 35 },
        { "pattern": "{{number:1-2000}}. Sokak", "weight": 30 }
      ]
    },
    "user_email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}{{number:1-999}}@example.com",
      "unique": true,
      "unique_fallback": "user_{{uuid}}@example.com"
    },
    "slug": {
      "type": "template",
      "pattern": "{{product_name}}",
      "operations": ["slugify", "lowercase"]
    },
    "full_address": {
      "type": "template",
      "variants": [
        {
          "pattern": "{{street_name}} No:{{number:1-200}} {{district}}/{{city}}",
          "weight": 45,
          "condition": "locale == 'tr_TR'"
        },
        {
          "pattern": "{{number:1-200}} {{street_name}}, {{city}}, {{state}} {{zip}}",
          "weight": 55,
          "condition": "locale == 'en_US'"
        }
      ],
      "unique": false,
      "unique_within": null,
      "operations": [],
      "nullable": false,
      "description": "Full mailing address"
    }
  }
}

Statistical Generator

Statistical generators produce data matching real-world distributions.

json
{
  "generators": {
    "order_status": {
      "type": "statistical",
      "mode": "categorical",
      "values": [
        { "value": "completed", "weight": 70 },
        { "value": "pending", "weight": 20 },
        { "value": "cancelled", "weight": 10 }
      ]
    },
    "customer_age": {
      "type": "statistical",
      "mode": "continuous",
      "distribution": "normal",
      "params": { "mean": 35, "stddev": 12 },
      "constraints": { "min": 18, "max": 85 }
    },
    "salary": {
      "type": "statistical",
      "mode": "continuous",
      "distribution": "lognormal",
      "params": { "mu": 10.5, "sigma": 0.8 },
      "differential_privacy": {
        "enabled": true,
        "epsilon": 1.0
      }
    },
    "order_totals": {
      "type": "statistical",
      "mode": "algebraic",
      "columns": ["subtotal", "tax", "discount", "total"],
      "relationship": "total = subtotal + tax - discount"
    }
  }
}

Available modes:

ModeDescriptionParameters
categoricalPreserve frequency distributionvalues with weights
continuousFollow statistical distributiondistribution, params, constraints
algebraicPreserve mathematical relationshipscolumns, relationship
multivariatePreserve correlationscolumns, correlations

Available distributions: normal, lognormal, exponential, uniform, poisson, beta, gamma

Linked Generator

Linked generators ensure related columns generate coherent data together.

json
{
  "generators": {
    "location": {
      "type": "linked",
      "columns": ["city", "district", "country", "postal_code", "latitude", "longitude", "phone_prefix", "currency"],
      "source": "lists/geo/locations.json"
    },
    "person_info": {
      "type": "linked",
      "columns": ["first_name", "gender", "title"],
      "source": "lists/person/names_with_gender.json",
      "rules": {
        "title": {
          "male": ["Bay", "Mr."],
          "female": ["Bayan", "Ms.", "Mrs."]
        }
      }
    },
    "financials": {
      "type": "linked",
      "columns": ["salary", "bonus", "tax", "net_income"],
      "rules": {
        "salary": { "type": "statistical", "distribution": "lognormal", "params": { "mu": 10, "sigma": 0.5 } },
        "bonus": "salary * uniform(0.05, 0.20)",
        "tax": "(salary + bonus) * 0.25",
        "net_income": "salary + bonus - tax"
      }
    }
  },
  "entities": {
    "Employee": {
      "fields": {
        "city": { "generator": "location.city" },
        "country": { "generator": "location.country" },
        "salary": { "generator": "financials.salary" },
        "net_income": { "generator": "financials.net_income" }
      }
    }
  }
}

Event Sequence Generator

Event sequences generate chronologically valid date/time series.

json
{
  "generators": {
    "order_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
        },
        {
          "name": "delivered_at",
          "after": "shipped_at",
          "delay": { "min": "1d", "max": "7d" },
          "probability": 0.85
        }
      ]
    }
  },
  "entities": {
    "Order": {
      "fields": {
        "created_at": { "generator": "order_timeline.created_at" },
        "paid_at": { "generator": "order_timeline.paid_at" },
        "shipped_at": { "generator": "order_timeline.shipped_at" },
        "delivered_at": { "generator": "order_timeline.delivered_at" }
      }
    }
  }
}

Event options:

OptionDescriptionExample
baseThe anchor event, generated firsttrue
afterThis event occurs after specified event"created_at"
delayTime range between events{ "min": "1d", "max": "7d" }
probabilityChance this event occurs (null if < 1.0)0.85
conditionOnly generate if condition met"status = 'paid'"

Entity Definitions

Entities represent data structures (like database tables).

json
{
  "entities": {
    "User": {
      "table": "users",
      "fields": {
        "id": {
          "generator": "user_id",
          "primary_key": true
        },
        "first_name": {
          "generator": "first_name"
        },
        "last_name": {
          "generator": "last_name"
        },
        "email": {
          "generator": "email",
          "unique": true
        },
        "age": {
          "generator": "age",
          "nullable": true
        },
        "created_at": {
          "generator": "created_at"
        },
        "is_active": {
          "type": "logic",
          "algorithm": "boolean",
          "params": { "probability": 0.85 }
        },
        "full_name": {
          "type": "template",
          "pattern": "{{self.first_name}} {{self.last_name}}"
        },
        "company_id": {
          "ref": "Company.id"
        }
      },
      "constraints": [
        { "type": "unique", "fields": ["email"] },
        { "type": "check", "expression": "age >= 18" }
      ],
      "indexes": [
        { "fields": ["email"], "unique": true },
        { "fields": ["company_id", "created_at"] }
      ]
    }
  }
}

Field Options

json
{
  "fields": {
    "field_name": {
      "generator": "generator_name",
      "primary_key": false,
      "unique": false,
      "nullable": false,
      "default": null,
      "description": "Field description"
    },
    "inline_field": {
      "type": "logic",
      "algorithm": "uuid_v7"
    },
    "foreign_key_field": {
      "ref": "Entity.field"
    },
    "computed_field": {
      "computed": "expression"
    }
  }
}

Relationship Definitions

Explicit relationship declarations for referential integrity.

json
{
  "relationships": [
    {
      "from": "Order.user_id",
      "to": "User.id",
      "cardinality": "many-to-one",
      "on_delete": "cascade"
    },
    {
      "from": "ProductCategory.product_id",
      "to": "Product.id",
      "cardinality": "many-to-one"
    },
    {
      "from": "ProductCategory.category_id",
      "to": "Category.id",
      "cardinality": "many-to-one"
    },
    {
      "from": "Employee.manager_id",
      "to": "Employee.id",
      "cardinality": "many-to-one",
      "nullable": true
    }
  ]
}

on_delete options: "cascade" (delete referencing rows), "set_null" (set FK to null), "restrict" (prevent deletion)

Cardinality Types

TypeDescriptionExample
one-to-oneEach A has exactly one BUser ↔ Profile
one-to-manyEach A has many BsUser → Orders
many-to-oneMany As belong to one BOrders → User
many-to-manyMany As to many BsProducts ↔ Categories

Scenario Definitions

Scenarios define how much data to generate.

json
{
  "scenarios": {
    "development": {
      "User": 100,
      "Product": 50,
      "Order": 500
    },
    "staging": {
      "User": 10000,
      "Product": 1000,
      "Order": 50000
    },
    "load_test": {
      "User": 100000,
      "Product": 5000,
      "Order": 1000000
    },
    "custom": {
      "User": {
        "count": 1000,
        "seed": 12345
      },
      "Product": {
        "count": 500,
        "filter": "category == 'electronics'"
      },
      "Order": {
        "count": 5000,
        "distribution": {
          "per_user": "gaussian",
          "params": { "mean": 5, "stddev": 2 }
        }
      }
    }
  }
}

Full Example

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "locale": "tr_TR",
  "name": "Turkish E-Commerce Platform",
  "description": "Complete e-commerce data generation schema",

  "metadata": {
    "author": "Phony Team",
    "version": "1.0.0",
    "license": "MIT",
    "tags": ["ecommerce", "turkish", "retail"]
  },

  "inherits": "base",

  "generators": {
    "uuid": {
      "type": "logic",
      "algorithm": "uuid_v7"
    },
    "timestamp": {
      "type": "logic",
      "algorithm": "datetime_between",
      "params": { "start": "-2years", "end": "now" }
    },
    "future_date": {
      "type": "logic",
      "algorithm": "date_between",
      "params": { "start": "now", "end": "+1year" }
    },
    "price": {
      "type": "logic",
      "algorithm": "float_between",
      "params": { "min": 10, "max": 50000, "precision": 2 }
    },
    "quantity": {
      "type": "logic",
      "algorithm": "int_between",
      "params": { "min": 1, "max": 10 }
    },
    "rating": {
      "type": "logic",
      "algorithm": "float_between",
      "params": { "min": 1, "max": 5, "precision": 1 }
    },
    "is_active": {
      "type": "logic",
      "algorithm": "boolean",
      "params": { "probability": 0.85 }
    },

    "city": {
      "type": "list",
      "source": "lists/geo/cities.json"
    },
    "district": {
      "type": "list",
      "source": "lists/geo/districts.json"
    },
    "category": {
      "type": "list",
      "source": "lists/commerce/categories.json"
    },
    "payment_method": {
      "type": "list",
      "source": "inline",
      "values": [
        { "value": "credit_card", "weight": 50 },
        { "value": "debit_card", "weight": 25 },
        { "value": "bank_transfer", "weight": 15 },
        { "value": "cash_on_delivery", "weight": 10 }
      ]
    },
    "order_status": {
      "type": "list",
      "source": "inline",
      "values": [
        { "value": "pending", "weight": 10 },
        { "value": "processing", "weight": 15 },
        { "value": "shipped", "weight": 20 },
        { "value": "delivered", "weight": 45 },
        { "value": "cancelled", "weight": 7 },
        { "value": "refunded", "weight": 3 }
      ]
    },

    "first_name": {
      "type": "model",
      "source": "models/person/first_names.ngram",
      "generation": { "mode": "word" }
    },
    "last_name": {
      "type": "model",
      "source": "models/person/last_names.ngram",
      "generation": { "mode": "word" }
    },
    "company_name": {
      "type": "model",
      "source": "models/business/company_names.ngram",
      "generation": { "mode": "word" }
    },
    "product_name": {
      "type": "model",
      "source": "models/commerce/product_names.ngram",
      "generation": { "mode": "word" }
    },
    "street_name": {
      "type": "model",
      "source": "models/address/street_names.ngram",
      "generation": { "mode": "word" }
    },

    "full_name": {
      "type": "template",
      "pattern": "{{first_name}} {{last_name}}"
    },
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@{{pick(['gmail.com', 'hotmail.com', 'yahoo.com', 'outlook.com'])}}",
      "unique": true
    },
    "phone": {
      "type": "template",
      "pattern": "+90 {{pick(['532', '533', '535', '542', '543', '544', '545'])}} {{pattern:### ## ##}}"
    },
    "address": {
      "type": "template",
      "variants": [
        { "pattern": "{{street_name}} No:{{number:1-200}} {{district}}/{{city}}", "weight": 40 },
        { "pattern": "{{street_name}} {{number:1-150}}/{{number:1-20}} {{city}}", "weight": 35 },
        { "pattern": "{{pattern:?????}} Mah. {{street_name}} No:{{number:1-100}} {{city}}", "weight": 25 }
      ]
    },
    "sku": {
      "type": "template",
      "pattern": "{{uppercase(substring(category.code, 0, 3))}}-{{pattern:######}}",
      "unique": true
    },
    "order_number": {
      "type": "template",
      "pattern": "ORD-{{format(now(), 'Ymd')}}-{{pattern:######}}",
      "unique": true
    }
  },

  "entities": {
    "User": {
      "table": "users",
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "first_name": { "generator": "first_name" },
        "last_name": { "generator": "last_name" },
        "full_name": { "generator": "full_name" },
        "email": { "generator": "email", "unique": true },
        "phone": { "generator": "phone" },
        "address": { "generator": "address" },
        "is_active": { "generator": "is_active" },
        "created_at": { "generator": "timestamp" }
      },
      "indexes": [
        { "fields": ["email"], "unique": true }
      ]
    },
    "Category": {
      "table": "categories",
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "name": { "generator": "category" },
        "slug": { "type": "template", "pattern": "{{slugify(self.name)}}" },
        "parent_id": { "ref": "Category.id", "nullable": true },
        "created_at": { "generator": "timestamp" }
      }
    },
    "Product": {
      "table": "products",
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "sku": { "generator": "sku", "unique": true },
        "name": { "generator": "product_name" },
        "description": { "type": "template", "pattern": "{{product_name}} - Yüksek kaliteli ürün" },
        "price": { "generator": "price" },
        "category_id": { "ref": "Category.id" },
        "is_active": { "generator": "is_active" },
        "rating": { "generator": "rating" },
        "created_at": { "generator": "timestamp" }
      },
      "indexes": [
        { "fields": ["sku"], "unique": true },
        { "fields": ["category_id"] }
      ]
    },
    "Order": {
      "table": "orders",
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "order_number": { "generator": "order_number", "unique": true },
        "user_id": { "ref": "User.id" },
        "status": { "generator": "order_status" },
        "payment_method": { "generator": "payment_method" },
        "shipping_address": { "generator": "address" },
        "subtotal": { "computed": "SUM(order_items.line_total)" },
        "tax": { "computed": "subtotal * 0.18" },
        "total": { "computed": "subtotal + tax" },
        "ordered_at": { "generator": "timestamp" },
        "delivered_at": {
          "type": "logic",
          "algorithm": "datetime_between",
          "params": { "start": "self.ordered_at", "end": "self.ordered_at + 14days" },
          "nullable": true
        }
      },
      "indexes": [
        { "fields": ["order_number"], "unique": true },
        { "fields": ["user_id"] }
      ]
    },
    "OrderItem": {
      "table": "order_items",
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "order_id": { "ref": "Order.id" },
        "product_id": { "ref": "Product.id" },
        "quantity": { "generator": "quantity" },
        "unit_price": { "type": "template", "pattern": "{{ref:Product.price}}" },
        "line_total": { "computed": "quantity * unit_price" }
      },
      "indexes": [
        { "fields": ["order_id"] },
        { "fields": ["product_id"] }
      ]
    }
  },

  "relationships": [
    { "from": "Category.parent_id", "to": "Category.id", "cardinality": "many-to-one", "nullable": true },
    { "from": "Product.category_id", "to": "Category.id", "cardinality": "many-to-one" },
    { "from": "Order.user_id", "to": "User.id", "cardinality": "many-to-one" },
    { "from": "OrderItem.order_id", "to": "Order.id", "cardinality": "many-to-one", "on_delete": "cascade" },
    { "from": "OrderItem.product_id", "to": "Product.id", "cardinality": "many-to-one" }
  ],

  "scenarios": {
    "development": {
      "User": 50,
      "Category": 10,
      "Product": 100,
      "Order": 200,
      "OrderItem": 500
    },
    "staging": {
      "User": 5000,
      "Category": 50,
      "Product": 2000,
      "Order": 10000,
      "OrderItem": 30000
    },
    "production_mirror": {
      "User": { "count": 100000, "seed": 12345 },
      "Category": 100,
      "Product": 10000,
      "Order": {
        "count": 500000,
        "distribution": { "per_user": "gaussian", "params": { "mean": 5, "stddev": 3 } }
      },
      "OrderItem": {
        "count": 1500000,
        "distribution": { "per_order": "gaussian", "params": { "mean": 3, "stddev": 1 } }
      }
    },
    "load_test": {
      "User": 1000000,
      "Category": 100,
      "Product": 50000,
      "Order": 5000000,
      "OrderItem": 15000000
    }
  }
}

Validation Rules

PDL schemas are validated against these rules:

RuleDescription
Version requiredversion field must be present
Name requiredname field must be present
Valid generatorsAll generator references must exist
Valid refsAll ref: references must point to existing entity fields
No circular refsEntity references cannot be circular
Unique constraintsFields with unique: true must have sufficient entropy
Valid algorithmsLogic generator algorithms must be recognized
Valid sourcesList/model sources must exist in package

Edge Cases & Error Handling

Generator Reference Doesn't Exist

bash
$ phony validate schema.pdl.json

Error: Unknown generator reference 'nonexistent_generator'
 at entities.User.fields.name.generator
 Did you mean: 'first_name', 'last_name'?

Circular Template References

json
{
  "generators": {
    "a": { "type": "template", "pattern": "{{b}}" },
    "b": { "type": "template", "pattern": "{{a}}" }
  }
}
bash
Error: Circular reference detected
 a b a
 Break the cycle by using a non-template generator

Empty Variants Array

json
{
  "generators": {
    "address": { "type": "template", "variants": [] }
  }
}
bash
Error: Template generator must have at least 1 variant
 at generators.address.variants

Invalid Locale Code

json
{
  "locale": "invalid_LOCALE"
}
bash
Warning: Unknown locale 'invalid_LOCALE'
 Falling back to 'en_US'
 Valid formats: 'en_US', 'tr_TR', 'de_DE'

Uniqueness Exhaustion

When a unique constraint can't be satisfied:

json
{
  "generators": {
    "status": {
      "type": "list",
      "source": "inline",
      "values": ["a", "b", "c"],
      "unique": true
    }
  }
}

Generating 100 records will fail after 3:

bash
Error: Cannot generate unique value for 'status'
 3 unique values available, 100 requested
 Add more values or remove 'unique: true'

For templates, use unique_fallback:

json
{
  "email": {
    "type": "template",
    "pattern": "{{lowercase(first_name)}}@example.com",
    "unique": true,
    "unique_fallback": "user{{number:10000-99999}}@example.com"
  }
}

Self-Referential Entities

Self-referential relationships (like categories with parent) require nullable: true:

json
{
  "entities": {
    "Category": {
      "fields": {
        "id": { "generator": "uuid", "primary_key": true },
        "parent_id": { "ref": "Category.id", "nullable": true }
      }
    }
  }
}

Without nullable: true, you'd have infinite recursion.

Package Dependency Conflicts

When two packages require incompatible versions:

bash
Error: Dependency conflict
 @phony/ecommerce requires @phony/base ^1.0.0
 @phony/healthcare requires @phony/base ^2.0.0
 Cannot resolve compatible version

Resolution: Update packages or use explicit version override in manifest


CLI Validation

Use the phony CLI to validate schemas before building:

bash
# Validate a schema file
phony validate schema.pdl.json

# Validate with verbose output
phony validate schema.pdl.json --verbose

# Validate a built package
phony validate my-package.phony

What gets validated:

  • JSON syntax and structure
  • JSON Schema compliance
  • Generator references (all {{generator_name}} must exist)
  • Entity references (all ref: must point to valid fields)
  • Circular dependency detection
  • Source file existence (list/model files)

Example output:

bash
$ phony validate schema.pdl.json

 JSON syntax valid
 Schema structure valid
 12 generators defined
 5 entities defined
 All generator references resolved
 All entity references resolved
 No circular dependencies
 All source files exist

Schema valid! Ready to build.

Phony Cloud Platform Specification