Skip to content

How to generate N items per parent ​

You want each generated record to carry a variable-length array of child values: a handful of tags on a user, several line-items on an order, a few phone numbers on a contact. The repeat step inside a composition produces exactly that — an array, drawn deterministically from a seed.

Steps ​

  1. Add a repeat step to a composition's let block. Its shape is { "repeat": { "count": …, "of": … } }:
    • count — how many items to produce (see the variations below).
    • of — the generator to invoke for each item (any PGDL generator).
  2. Reference the step's name from the composition's output (or body). The step is bound to the resulting array.
  3. Load the package with --package and run it with --use.

Complete example ​

A user with three tags. Save this as repeat-pkg/phony.json:

json
{
  "name": "@demo/repeat",
  "version": "0.1.0",
  "generators": [
    { "name": "@demo/repeat:user_with_tags",
      "let": {
        "tags": { "repeat": { "count": 3, "of": {
          "type": "list", "source": "inline",
          "values": ["urgent","new","sale","vip","beta"] } } }
      },
      "output": { "id": "{{ pattern:U#### }}", "tags": "{{ tags }}" } }
  ]
}

Run it:

bash
phony generate --use @demo/repeat:user_with_tags --package ./repeat-pkg --seed 42 -n 2

Actual output:

json
[
  {
    "id": "U6377",
    "tags": [
      "sale",
      "beta",
      "beta"
    ]
  },
  {
    "id": "U0248",
    "tags": [
      "new",
      "sale",
      "sale"
    ]
  }
]

Each item is drawn independently, so repeats within one array are expected (see unique to forbid them).

Variations: the four ways to set count ​

count accepts a literal, a range string, or a PEL expression.

A fixed number ​

json
{ "repeat": { "count": 3, "of": { "type": "logic", "algorithm": "int_between", "params": { "min": 1, "max": 99 } } } }

Always three items.

A range "a-b" (drawn per record) ​

A string "a-b" draws an inclusive count per invocation, so different rows get different lengths:

json
{ "name": "@demo/repeat2:cart_range",
  "let": {
    "items": { "repeat": { "count": "1-4", "of": {
      "type": "logic", "algorithm": "int_between", "params": { "min": 1, "max": 99 } } } }
  },
  "output": { "items": "{{ items }}" } }
bash
phony generate --use @demo/repeat2:cart_range --package ./repeat2 --seed 1 -n 3
json
[
  { "items": [ 63 ] },
  { "items": [ 60, 1, 61 ] },
  { "items": [ 90, 11, 30 ] }
]

One, three, three — each row's length is its own draw, always within 1..=4.

A PEL expression (count from a param or let) ​

Any other string is a PEL expression evaluated against the composition's locals, so the count can come from a param:

json
{ "name": "@demo/repeat2:order",
  "params": { "n_items": { "type": "integer", "default": 3 } },
  "let": {
    "lines": { "repeat": { "count": "n_items", "of": {
      "type": "list", "source": "inline",
      "values": ["Widget","Gadget","Sprocket","Cog","Bolt"] } } }
  },
  "output": { "order_id": "{{ pattern:ORD-#### }}", "lines": "{{ lines }}" } }

Pass the param through an input file (--use can't carry params) — save as order.json:

json
{ "use": "@demo/repeat2:order", "params": { "n_items": 5 } }
bash
phony generate ./order.json --package ./repeat2 --seed 7 -n 1
json
[
  {
    "lines": [ "Sprocket", "Cog", "Cog", "Sprocket", "Widget" ],
    "order_id": "ORD-6857"
  }
]

With n_items omitted the default (3) applies.

Distinct items (unique: true) ​

Add "unique": true to de-duplicate an array. Items are re-rolled until each is distinct:

json
{ "name": "@demo/repeat2:distinct_tags",
  "let": {
    "tags": { "repeat": { "count": 3, "unique": true, "of": {
      "type": "list", "source": "inline",
      "values": ["urgent","new","sale","vip","beta"] } } }
  },
  "output": { "tags": "{{ tags }}" } }
bash
phony generate --use @demo/repeat2:distinct_tags --package ./repeat2 --seed 42 -n 2
json
[
  { "tags": [ "sale", "beta", "new" ] },
  { "tags": [ "new", "sale", "beta" ] }
]

Items that reference the parent ​

The of generator's params are PEL templates evaluated against the composition's locals, so an item can read a parent value. Here every line's price is drawn from a per-record base:

json
{ "name": "@demo:order_lines",
  "let": {
    "base": { "computed": "100" },
    "lines": { "repeat": { "count": 2, "of": {
      "type": "logic", "algorithm": "int_between",
      "params": { "min": "{{ base }}", "max": "{{ base }}" } } } }
  },
  "output": { "lines": "{{ lines }}" } }

Both items read base, so both are 100.

Nesting ​

of can be { "use": "@your/other:composition" }, so a repeat of a composition nests arrays of structured records without limit — e.g. an order that repeats line-items, each line-item itself an output object.

Pitfalls ​

  • count must resolve to a number. A literal integer, an "a-b" range string, or an expression that evaluates to a number. A range whose bounds aren't integers is treated as a plain expression, not a range.
  • unique: true can run out of room. If the item generator's domain is smaller than count, Phony gives up after a bounded number of retries and fails with a clear error — widen the item generator's range.
  • A negative or zero count yields an empty array, never an error.
  • References are checked at load time. A typo in the of generator's params (a name that isn't a param or let) fails when the package loads, not mid-generation.

See also ​

Phony Cloud — Documentation & Specification