Skip to content

@workflow/web: Decompose workflow-api-client.ts into focused modules and add test infrastructure#1070

Merged
VaguelySerious merged 16 commits intovercel:mainfrom
alizenhom:ali/enhance-o11y
Feb 24, 2026
Merged

@workflow/web: Decompose workflow-api-client.ts into focused modules and add test infrastructure#1070
VaguelySerious merged 16 commits intovercel:mainfrom
alizenhom:ali/enhance-o11y

Conversation

@alizenhom
Copy link
Copy Markdown
Contributor

@alizenhom alizenhom commented Feb 15, 2026

Summary

  • Refactors the monolithic workflow-api-client.ts (~1,330 lines) into 8 focused, single-responsibility modules while preserving a barrel re-export for backward compatibility
  • Sets up vitest + jsdom + @testing-library/react test infrastructure for packages/web and adds comprehensive unit tests across all extracted modules

Motivation

workflow-api-client.ts had grown to 1,329 lines with extensive duplication across 5 distinct patterns — paginated list hooks, exhaustive fetch helpers, merge-by-ID functions, poll functions, and server action wrappers — each repeated 2-3 times with only the fetch call or ID key varying. This made it difficult to test, navigate, and reason about individual concerns, and the package had no unit tests or test runner configured.

What Changed?

The monolith was split along natural boundaries into standalone modules:

  • workflow-errors.ts —> WorkflowWebAPIError class, getErrorMessage, unwrapServerActionResult, and unwrapOrThrow helpers for consistent error handling across the package
  • workflow-primitives.ts —> Pure utility functions (mergeById, fetchAllPaginated, pollResource) that provide the generic pagination and polling building blocks used by the hooks
  • workflow-actions.ts —> Thin wrappers around server actions (cancelRun, recreateRun, reenqueueRun, wakeUpRun, resumeHook) with uniform error unwrapping
  • workflow-streams.ts —> readStream and listStreams for fetching workflow stream data via the Fetch API
  • Four React hooks were extracted into hooks/:
    • use-paginated-list.ts —> Generic usePaginatedList hook with page caching and navigation, plus concrete useWorkflowRuns and useWorkflowHooks hooks built on top of it
    • use-resource-data.ts —> useWorkflowResourceData for fetching individual run/step/hook/sleep detail views with auto-refresh
    • use-trace-viewer.ts —> useWorkflowTraceViewerData for the trace viewer, combining run, steps, hooks, and events with live polling and exhaustive pagination
    • use-workflow-streams.ts —> useWorkflowStreams for polling the list of stream IDs on a run
      workflow-api-client.ts is now a ~39-line barrel that re-exports everything, so existing consumers are unaffected.

Test infrastructure

  • Added vitest, jsdom, and @testing-library/react as dev dependencies
  • Created vitest.config.ts with jsdom environment and ~ path alias
  • Added pnpm test script (pnpm test)

Test coverage

Added 8 test files organized to mirror the module structure:

Utilities & core logic

  • workflow-errors.test.ts —> error class construction, 403 message handling, unwrapOrThrow success/failure/rejection paths
  • workflow-primitives.test.ts —> mergeById deduplication and custom ID keys, fetchAllPaginated multi-page traversal and error bail-out, pollResource with both always and onHasMore cursor strategies
  • workflow-actions.test.ts —> success and failure cases for all 5 action wrappers
  • workflow-streams.test.ts —> readStream with startIndex/abort signal/non-ok responses/null body, listStreams success and structured error parsing

React hooks (using @testing-library/react + jsdom)

  • use-paginated-list.test.ts —> initial fetch, error propagation, parameter forwarding, cursor-based nextPage, previousPage cache hit, and reload (covers both useWorkflowRuns and useWorkflowHooks)
  • use-resource-data.test.ts —> run/step/hook/sleep resource types, enabled: false short-circuit, error states
  • use-trace-viewer.test.ts —> combined initial load of run + steps + hooks + events, paginated step population, error handling
  • use-workflow-streams.test.ts—> initial fetch and error handling

Notes

Apologies for the size of this PR, the extraction touched a lot of files but the actual logic is unchanged. Happy to break it up differently or address any feedback.

As a new contributor still getting familiar with the codebase, there may be rough edges I've missed. I'd appreciate any guidance on the following directions I'm interested in exploring next:

  • Convex world — implementing a new world for convex.
  • Observability improvements — continuing to expand test coverage, refactoring, and improving latency in the web package

Would love to know if either of these aligns with the project's priorities!

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 15, 2026

🦋 Changeset detected

Latest commit: 02a0cae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@workflow/web Patch
@workflow/cli Patch
workflow Patch
@workflow/world-testing Patch
@workflow/core Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/web-shared Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Feb 15, 2026

@alizenhom is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

@VaguelySerious
Copy link
Copy Markdown
Member

@alizenhom Thanks for contributing! We'll try to review this soon

I'd appreciate any guidance [for these projects]

I think all the things you listed are great and helpful. We'll happily take any observability and testing improvements.

For creating a Convex world, note that we don't take any community worlds into the main package. Instead, we recommend publishing your own NPM package, and then adding the package and description to worlds-manifest.json in this repository, which will add it to the docs, and automatically run benchmarks and e2e tests against it.
Feel free to propose any changes in this repository that make it easier for you to support/test/integrate the Convex world.

Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still reviewing, but a comment ahead of time: would you be able to move all the new client-related files into a sub-folder app/lib/client?

@alizenhom
Copy link
Copy Markdown
Contributor Author

alizenhom commented Feb 23, 2026

@VaguelySerious Thanks for the response!

Still reviewing, but a comment ahead of time: would you be able to move all the new client-related files into a sub-folder app/lib/client?

sure!

Meanwhile will work on more o11y and testing improvements, on the side will work also on the convex world and make a PR to update manifest.json when it's ready.

@alizenhom alizenhom requested a review from a team as a code owner February 24, 2026 13:34
* Unwraps a server action result, throwing WorkflowWebAPIError on failure.
* Use for simple action wrappers where you just want the result or an exception.
*/
export async function unwrapOrThrow<T>(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally of this seems equivalent to the previous happy path, but in error paths the error wrapping is slightly different: the new generic usePaginatedList catch block wraps non-Error
exceptions into WorkflowWebAPIError with layer: 'client' instead of preserving the layer from the server action error. Is this intentional?

Copy link
Copy Markdown
Contributor Author

@alizenhom alizenhom Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!, the behavior is equivalent on all reachable code paths because fetchFn is always wrapped in unwrapOrThrow, which only ever throws WorkflowWebAPIError (an Error subclass). The layer: 'client' fallback only applies to non-Error exceptions, which can't arise here.

import { resolve } from 'node:path';
import { defineConfig } from 'vitest/config';

export default defineConfig({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package.json needs a test script to be picked up in CI. Add "test": "vitest run" to packages/web/package.json to fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

packages/web/package.json already has this script "test": "vitest run".
check it here

Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's a lockfile issue - could you resole that? Then it'd be ready to merge

@socket-security
Copy link
Copy Markdown

socket-security bot commented Feb 24, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​jsdom@​26.1.0971001009570
Addednpm/​@​testing-library/​react@​16.3.29910010090100

View full report

@alizenhom
Copy link
Copy Markdown
Contributor Author

@VaguelySerious Thanks! pnpm-lock should be resolved now

@VaguelySerious VaguelySerious merged commit a0b99c8 into vercel:main Feb 24, 2026
66 of 101 checks passed
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.

2 participants