Repository navigation
Conversation
stopWhen(items, predicate) stops pulling from any iterable once the predicate is truthy, checked before the item is offered downstream. Promoted from nucube-app where it replaced hand-rolled stop logic in the MusicBrainz enrichment pipeline. - check before yield: the triggering item is never processed - pull-lazy: abandoned sources are left mid-stream, cleanup still runs - clean completion: failure: false, empty sourceErrors — cancel is a shorter run, not a source death; predicate throws are source errors - predicate receives (item, index), sync only, defaults to () => false - forwards numeric length from array sources so series keeps progress totals without an explicit total option - composes with series/scan/reduce/filter/findSync and raw for await with zero changes to those functions Also fixes filter/filterSync/findSync arg sniffing: iterable objects (generators, custom adapters) were misread as where() patterns because any non-array object matched. Patterns are now objects that do not implement an iteration protocol.
The default `() => false` is a pass-through, not an identity wrapper.
- examples.md: onProgress referenced an undefined onItem, and
enrichLibrary awaited series without returning its report, so the
{ results } destructure was always undefined
- reference.md: stopWhen example imported only series
- guide.md: sync-counterparts list omitted stopWhenSync
ildella
left a comment
There was a problem hiding this comment.
APPROVED
stopWhen / stopWhenSync source adapters and the isPatternObject fix are sound: functional, code-quality, and security passes found nothing, and the full suite (400 tests) plus lint pass locally on head 8c10512.
Fixed in this review (8f0c11e) — three docs examples that didn't run as written:
- docs/examples.md: the stopWhen snippet passed an undefined
onItemtoonProgressandenrichLibrarydidn't return the series report, so{ results }was always undefined - docs/reference.md: the stopWhen example imported only
series - docs/guide.md: the sync-counterparts list omitted
stopWhenSync
CI note: the "Code Check" workflow did not run on the new head. Pushes to this PR (including an empty commit) produce only the GitHub Advanced Security scan; the pull_request-triggered workflow last ran on 147142b (2026-08-23, green). Verified locally instead: yarn test 400 passed, yarn lint clean, on 8c10512. Worth checking why pull_request events stopped firing for this PR before relying on the gate again.
Master landed join() (0.10.0) and moved the filter/findSync pattern check into parseSearchArgs (src/search.js), whose isPattern lacked the iterable exclusion. Kept master's structure and folded the exclusion into isPattern, so filter(pages(), pred) still runs instead of returning a curried where(). Dropped the now-unused isPatternObject from shared.js. Docs: both stopWhen and join stay in the guide list and the skill; version is 0.10.1 since stopWhen ships after join.
What
New source adapter, promoted from nucube-app where it already replaced hand-rolled stop logic in the MusicBrainz enrichment pipeline (second time the pattern was requested by a consumer):
```js
export const stopWhen = (items, predicate = () => false) => ({
async * [Symbol.asyncIterator] () {
for await (const item of items) {
if (predicate(item))
return
yield item
}
},
})
// + stopWhenSync with Symbol.iterator
```
Usage — cancellation lives in the source, everything else stays in its own place:
```js
await series(enrichOne, {
total: albums.length,
pause: ENRICH_DELAY_MS,
pauseOnErrors: true,
onProgress: onItem,
})(stopWhen(albums, shouldStop))
stopWhen(folders, () => count >= limit) // limits close over counters
```
Contract (pinned by tests)
Design notes for reviewers
Out of scope (explicitly)
Testing
34 new tests (`tests/stop-when.test.js`, `tests/stop-when-sync.test.js`): nucube's 7 ported verbatim in intent + composition (series/reduce/filter/take), length forwarding, index passing, closure predicates, predicate-throws-as-source-error, generator cleanup. Full suite: 400 passed. Lint clean.
Docs
reference.md (new entry + TOC), guide.md, patterns.md (Pattern 9), examples.md (MusicBrainz-shaped example), architecture.md (one sentence under Eager Execution), migration.md (0.9.2 section incl. the filter fix note), skills/core/SKILL.md, README. Version bumped to 0.9.2.
Follow-up (not here)
nucube-app can delete `src/lib/iterables/stop-when.js` and import from `pipelean@0.9.2` with no call-site change; it may then drop its explicit `total: albums.length`.