Skip to content

refactor: separate task execution from collection tracking - #59

Merged
stromseng merged 1 commit into
maintenance/type-ownershipfrom
maintenance/task-execution
Sep 6, 2026
Merged

stromseng merged 1 commit into
maintenance/type-ownershipfrom
maintenance/task-execution

Conversation

@stromseng

@stromseng stromseng commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

Problem

The public API file mixes service provisioning, task overload adaptation, collection execution, outcome counters, and an unusual result-mode completion rule. To follow Progress.all, a maintainer must unpack a nested exit handler while keeping automatic scope management in mind. The single helper in utils.ts also has no obvious owner.

Solution

Organize execution around three concrete responsibilities:

  • api/provide-progress.ts reuses an existing Progress service or provides a scoped instance around the operation.
  • api/task.ts adapts the public single-task entry point to the service's task runner.
  • api/collections.ts keeps all, forEach, their execution types, and shared outcome accounting together. trackEffectOutcome names the per-item counting responsibility. completeAccountedResultTask names the exceptional result-mode completion rule.

Move inferTotal from utils.ts to api/infer-total.ts. Explicit API and package-root exports preserve the existing functions and option/return types. Update the service comment to point to the task entry point's new location.

Examples

Following nested execution

Progress.task(effect, options) enters api/task.ts, which uses provideProgress and delegates to progress.task. To investigate reuse versus an isolated service, open provide-progress.ts; the collection execution code no longer contains that implementation.

Understanding result-mode defects

For Progress.all([Effect.die(defect)], { description: "work", mode: "result" }), the one unit is accounted for as failed. The collection still exits with the defect, while its task is marked done because all work was accounted for. With another unprocessed unit, the task remains eligible for failure finalization instead. completeAccountedResultTask makes that distinction explicit without changing the effect exit.

Counting interrupted work

Both collection helpers use trackEffectOutcome: successful work increments succeeded, failed work increments failed, and interruption-only exits do not increment either counter. That policy remains shared rather than duplicated across all and forEach.

Validation

  • bun test: 130 passed, 0 failed, 396 assertions.
  • bun run check: typecheck, lint, formatting, and Knip passed.
  • bun run format applied; staged diff whitespace check passed.

Existing tests exercise defects in default/result modes, interruption, explicit finalization, service reuse, tuple/record inference, pipe forms, and total inference. Non-failing TSGO suggestions remain in existing schema definitions/tests. No build or dev server was run.

Review notes

This layer follows type ownership. Start with completeAccountedResultTask and its call site, then the small task/provisioning modules. The collection algorithms, options, public overloads, and scope boundaries are retained; no new generic execution framework is introduced.

Stack

GitHub stack #63: main → #58 (types) → #59 (execution) → #60 (ETA) → #61 (tests) → #62 (examples). Each PR is based on the layer below it, so its diff contains only that concern. Manage the chain with gh stack; no PR has been merged by this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant