feat(flow): object-read can fan out one item per object - #2237
Merged
Conversation
I shipped the read node returning a LIST under `output`, tried to build the
reaper it exists for, and it did not compose.
Every other node in the engine acts per item, and nothing expands a list inside
an item's json back into items — `openregister.loop` batches the items on the
walk, not the entries of a field. So a read that returns `{objects: [...]}` can
be counted and branched on, and cannot be acted on one row at a time.
Which is the motivating case. A reaper reads stale locks and then DELETES each
one; with the list shape its delete step sees a single item whose `uuid` is not
a field, and fails with "the match value for uuid could not be resolved from
the item". Measured, not predicted.
`fanOut: true` emits one item per object, carrying the incoming record onto each
so the run keeps what it was holding. No matches means no items, which ends the
branch — the same contract `openregister.loop` has for an empty input, and not
an error, because "nothing to reap" is the ordinary case.
The list stays the DEFAULT: it is the right shape for the other half of the uses
— "how many are there", "is there any" — where fanning out turns one decision
into N. Both shapes are pinned.
Refs #2235. 15,571 unit tests green (3 new); phpcs, phpstan clean.
Contributor
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 174/174 | |||
| npm | ✅ | ✅ 616/616 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-07-31 15:28 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #2236, which I shipped returning a list under
output— then tried to build the reaper it exists for, and it did not compose.Why the list shape does not work for the motivating case
Every other node in the engine acts per item, and nothing expands a list inside an item's json back into items —
openregister.loopbatches the items on the walk, not the entries of a field.So a read returning
{objects: [...]}can be counted and branched on, and cannot be acted on one row at a time.That is exactly what a reaper does: read stale locks, then delete each one. With the list shape its delete step sees a single item whose
uuidis not a field, and fails with:Measured while building it, not predicted.
The change
fanOut: trueemits one item per object, carrying the incoming record onto each so the run keeps what it was holding. No matches means no items, which ends the branch — the same contractopenregister.loophas for an empty input, and not an error, because "nothing to reap" is the ordinary case.The list stays the default. It is the right shape for the other half of the uses — "how many are there", "is there any" — where fanning out turns one decision into N. Both shapes are pinned by tests.
I flagged this exact trade-off in #2235 and picked the side that did not serve the case the node was built for.
Verification
15,571 unit tests green (3 new); phpcs (full tree, CI settings) and phpstan clean.