Skip to content

Convert billingOperationStore.ts to a discriminated union + owned transition function #14420

Description

@christian-byrne

Context

From Slack thread on #14396 (xstate/statechart question for billing ops). Same pattern as #14300 (workspace-switch state machine consolidation): imperative flags/status field plus separate per-transition side-effect functions, instead of a discriminated union with one owned transition/reducer function.

Problem

src/platform/workspace/stores/billingOperationStore.ts manages 3 operation types x 4 statuses (pending/succeeded/failed/timeout), 3 different timeout thresholds, recursive polling with backoff, and toast/telemetry/dialog side effects per terminal transition.

BillingOperation has a status: OperationStatus field but is not a true discriminated union — fields like actionUrl and authenticationRequiredSeen are typed as always-present but are only meaningful while status === 'pending'. Nothing in the type system prevents a 'succeeded' or 'timeout' operation from carrying a stale actionUrl.

handleSuccess(), handleFailure(), handleTimeout() are three separate functions, each independently doing: status update, cleanup (timers/toasts), telemetry, toast, and terminal-promise resolution. No single function owns "how does this operation reach a terminal state" — the shape is repeated 3 times with small variations, easy to drift.

Proposed fix

Convert BillingOperation into a real discriminated union on status, each variant carrying only its relevant fields (e.g. actionUrl/authenticationRequiredSeen only exist on the pending variant). Replace handleSuccess/handleFailure/handleTimeout with one transition function that takes the current operation and an outcome, returns the next operation state, and the side effects (cleanup/telemetry/toast/resolve) run once, driven by the transition result rather than duplicated per outcome.

No new dependency needed (skip xstate, per Slack discussion — team consensus was bundle size isn't the blocker, it's the added concept/onboarding cost, and a plain TS union + reducer gets most of the safety).

Reference

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions