Skip to content

feat(workflow-core): add createWorkflowFactory#8

Open
tannerlinsley wants to merge 1 commit into
mainfrom
taren/wizardly-haslett-6d80d4
Open

feat(workflow-core): add createWorkflowFactory#8
tannerlinsley wants to merge 1 commit into
mainfrom
taren/wizardly-haslett-6d80d4

Conversation

@tannerlinsley
Copy link
Copy Markdown
Member

@tannerlinsley tannerlinsley commented May 23, 2026

Summary

  • New createWorkflowFactory(config?) lets a family of workflows share middleware and step-retry defaults without repeating .middleware([...]) per workflow.
  • Factory middleware runs before per-workflow middleware; ctx extensions accumulate across both layers, with full type inference flowing through.
  • Per-workflow config wins over factory defaults. appWorkflow.extend({ ... }) forks a child factory with override defaults without mutating the parent.
const appWorkflow = createWorkflowFactory({
  defaultStepRetry: { maxAttempts: 3 },
}).middleware([traced, requireUser])

const onboard = appWorkflow({ id: 'onboard' })
  .middleware([requireEmailVerified])     // appended after factory mws
  .handler(async (ctx) => { /* ctx.trace, ctx.user, ctx.emailVerified */ })

const paid = appWorkflow
  .extend({ defaultStepRetry: { maxAttempts: 5 } })
  .middleware([requirePro])

Implementation wraps createWorkflow rather than reaching into builder internals, so the existing builder stays the single source of truth for ctx accumulation. The factory itself is a callable hybrid (function + .middleware() / .extend() methods) — drop-in shape for createWorkflow({ id }).

Test plan

  • npx vitest run tests/workflow-factory.test.ts — 9 new tests pass (ctx extension, execution order, ctx accumulation across both layers, defaults merge with per-workflow winning, .extend() fork + overrides, factory .middleware() chaining, no-extra-mw case, empty-factory equivalence)
  • npx vitest run — full 114-test suite passes (no regressions)
  • npx tsc --noEmit — clean
  • npx nx run @tanstack/workflow-core:test:eslint — clean
  • Docs link verification — no new broken links
  • Manual: try the factory in an example app to sanity-check the DX

Summary by CodeRabbit

  • New Features

    • Introduced createWorkflowFactory to define reusable workflow configurations with shared middleware and default step-retry settings.
    • Factory middleware executes before per-workflow middleware; per-workflow configuration overrides factory defaults.
    • Create forked factories with .extend() without mutating parent configuration.
  • Documentation

    • Added workflow factory usage recipes and examples.
    • Updated middleware documentation with factory execution order rules.

Review Change Stack

…+ defaults

Pin shared middleware and step-retry defaults across a family of
workflows. Factory middleware runs before per-workflow middleware;
ctx extensions accumulate across both layers. Per-workflow config
wins over factory defaults. `.extend(overrides?)` forks a child
factory without mutating the parent.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

This PR introduces createWorkflowFactory, a new API for sharing middleware and default step-retry settings across a family of related workflows. Factories support method chaining via .middleware(), forking via .extend() for overriding defaults, and per-workflow overrides that take precedence over factory defaults. Factory middleware executes before per-workflow middleware.

Changes

Workflow Factory Implementation

Layer / File(s) Summary
Factory contracts and configuration types
packages/workflow-core/src/define/create-workflow-factory.ts (imports, types)
CreateWorkflowFactoryConfig interface defines optional defaultStepRetry field; WorkflowFactoryBuilder<TCtxExt> callable interface exposes .middleware(...) for appending shared middleware, .extend(overrides?) for forking child factories, and call signature returning typed WorkflowBuilder.
Factory builder implementation and export
packages/workflow-core/src/define/create-workflow-factory.ts (implementation, public export)
buildFactory maintains internal state (middlewares, defaults) and merges defaultStepRetry at workflow creation time, with per-workflow config taking precedence. Factory middlewares apply before workflow-specific middlewares via conditional chaining. .extend() returns new instances without mutating parent; createWorkflowFactory(config?) initializes with empty middlewares and provided defaults.
Public API export
packages/workflow-core/src/index.ts
Re-exports createWorkflowFactory and factory types (CreateWorkflowFactoryConfig, WorkflowFactoryBuilder) from ./define/create-workflow-factory.
Factory behavior validation
packages/workflow-core/tests/workflow-factory.test.ts
Comprehensive test suite verifying factory middleware application to all produced workflows, correct execution ordering (factory before per-workflow with before/after sequencing), context accumulation across both middleware layers with type safety, defaultStepRetry merging with per-workflow override precedence, extend() forking without parent mutation, middleware chaining composition, and empty factory equivalence to base createWorkflow.
User documentation and release notes
.changeset/workflow-factory.md, docs/concepts/middleware.md, docs/quick-start.md, packages/workflow-core/README.md
Changeset documents the minor version addition; middleware docs add execution-order rules and recipe showing shared middleware setup with factory; quick-start includes example demonstrating default retry config and factory extension; README updated to reference factory for middleware reuse across workflow families.

Sequence Diagram

sequenceDiagram
  participant Factory as createWorkflowFactory
  participant Builder as WorkflowBuilder
  participant Workflow as Produced Workflow
  Factory->>Factory: buildFactory(middlewares, defaults)
  Factory->>Builder: .middleware([...])
  Builder->>Builder: accumulate middlewares
  Builder->>Builder: return new builder instance
  Factory->>Builder: .extend(overrides)
  Builder->>Builder: shallow merge defaults
  Builder->>Builder: copy middlewares
  Builder->>Builder: return child factory
  Factory->>Workflow: call factory with config
  Workflow->>Workflow: merge defaultStepRetry
  Workflow->>Workflow: apply factory middleware first
  Workflow->>Workflow: apply per-workflow middleware
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A factory blooms with shared middleware cheer,
workflows inherit their defaults so dear,
extend them like branches, each one stands free,
no parent is mutated—just pure progeny! 🌱
TypeScript guards the flow with care so keen,
tests verify each middleware scene.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main addition: a new createWorkflowFactory feature for workflow-core.
Description check ✅ Passed The description comprehensively covers the feature, implementation approach, and validation; it follows the template structure with a detailed summary, example code, and test plan with clear checkmarks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/workflow-core/src/define/create-workflow-factory.ts`:
- Around line 127-130: createWorkflowFactory currently passes the caller's
config object by reference to buildFactory (defaults: config), risking mutation
leaks; fix this by defensively copying the config before passing it (e.g.,
create a shallow copy of CreateWorkflowFactoryConfig and pass that to
buildFactory) so createWorkflowFactory and buildFactory use an independent
defaults object and external mutations won't affect existing factories.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19dc9e00-5de0-4c68-8f88-69e0e96f3af1

📥 Commits

Reviewing files that changed from the base of the PR and between 0de8e83 and fe84106.

📒 Files selected for processing (7)
  • .changeset/workflow-factory.md
  • docs/concepts/middleware.md
  • docs/quick-start.md
  • packages/workflow-core/README.md
  • packages/workflow-core/src/define/create-workflow-factory.ts
  • packages/workflow-core/src/index.ts
  • packages/workflow-core/tests/workflow-factory.test.ts

Comment on lines +127 to +130
export function createWorkflowFactory(
config: CreateWorkflowFactoryConfig = {},
): WorkflowFactoryBuilder<unknown> {
return buildFactory({ middlewares: [], defaults: config })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Defensively copy initial config to avoid mutation leaks.

Line 130 stores the caller’s config object by reference. If that object is mutated later, existing factories can change behavior unexpectedly.

Suggested fix
 export function createWorkflowFactory(
   config: CreateWorkflowFactoryConfig = {},
 ): WorkflowFactoryBuilder<unknown> {
-  return buildFactory({ middlewares: [], defaults: config })
+  return buildFactory({ middlewares: [], defaults: { ...config } })
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function createWorkflowFactory(
config: CreateWorkflowFactoryConfig = {},
): WorkflowFactoryBuilder<unknown> {
return buildFactory({ middlewares: [], defaults: config })
export function createWorkflowFactory(
config: CreateWorkflowFactoryConfig = {},
): WorkflowFactoryBuilder<unknown> {
return buildFactory({ middlewares: [], defaults: { ...config } })
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workflow-core/src/define/create-workflow-factory.ts` around lines
127 - 130, createWorkflowFactory currently passes the caller's config object by
reference to buildFactory (defaults: config), risking mutation leaks; fix this
by defensively copying the config before passing it (e.g., create a shallow copy
of CreateWorkflowFactoryConfig and pass that to buildFactory) so
createWorkflowFactory and buildFactory use an independent defaults object and
external mutations won't affect existing factories.

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