Recipes

Append to a List

Submit-and-review flows are everywhere: place an order, see it in the history; send an event, see it in the feed. This recipe accepts items with POST and returns everything submitted so far with GET, using flags as an append-only store.

The recipe

mock "POST /purchases" {
format = "json"
body = <<-EOF
{{= $id (uuid)}}
{{setFlag (append 'purchases:' $id) request.body}}
{{= $ids (getFlag 'purchases:ids')}}
{{#if $ids}}
{{setFlag 'purchases:ids' (append $ids ',' $id)}}
{{else}}
{{setFlag 'purchases:ids' $id}}
{{/if}}
{
"id": "{{$id}}",
"status": "created"
}
EOF
}

mock "GET /purchases" {
format = "json"
body = <<-EOF
{{= $ids (getFlag 'purchases:ids')}}
[
{{#if $ids}}
{{#forEach (split $ids ',')}}
{{json (getFlag (append 'purchases:' item))}}{{^isLast}},{{/isLast}}
{{/forEach}}
{{/if}}
]
EOF
}

Try it

$curl -X POST http://localhost:8080/purchases -H 'Content-Type: application/json' -d '{"product": "Widget", "qty": 2}'
{ "id": "7d4c2f2e-9b1a-4f7e-8a3d-5e6f7a8b9c0d", "status": "created" }
$curl http://localhost:8080/purchases
[ { "product": "Widget", "qty": 2 } ]

How it works

  • Every submission gets a fresh id from the uuid helper, and the whole request body is stored as an object under purchases:<id>.
  • purchases:ids accumulates the ids as a comma-separated string. The first submission is the special case: appending to a missing flag would start the list with a comma, so the if branch sets it directly.
  • The GET splits the id list back into an array, looks each record up, and renders it with the json helper. The surrounding if keeps the response a valid empty array before anything is submitted.
  • POST mocks default to status 201, so no status field is needed.
Since nothing is ever removed, {{^isLast}},{{/isLast}} is safe here. If you add deletion, switch the list to the accumulator pattern from Stateful CRUD, which tolerates missing records.

Variations

  • Add a reset mock for test isolation: DELETE /purchases with {{delFlag 'purchases:ids'}} in the body.
  • Give entries a TTL by passing a third argument to both setFlag calls, and the feed cleans itself up after a while (combine with the accumulator pattern so expired records do not break commas).
  • Echo the stored item back with an id in each list entry by storing a composed object instead of the raw body; see the id note in Stateful CRUD.