Skip to content

feat(workflow): add wait/resolve support for human-in-the-loop workflows#987

Merged
toiroakr merged 27 commits into
mainfrom
feat/workflow-resolve
Apr 24, 2026
Merged

feat(workflow): add wait/resolve support for human-in-the-loop workflows#987
toiroakr merged 27 commits into
mainfrom
feat/workflow-resolve

Conversation

@toiroakr

@toiroakr toiroakr commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements SDK support for tailor.workflow.wait() and tailor.workflow.resolve() (platform-planning#995, function#154).

  • Add defineWaitPoint<P, R>(key) and defineWaitPoints(define => ({ key: define<P, R>() })) for typed wait/resolve points
  • WaitPointInstance.wait() and .resolve() runtime-delegate to globalThis.tailor.workflow on the platform
  • Add setupWaitPointMock test utility for local and bundled workflow tests
  • Add example/workflows/approval.ts, example/resolvers/resolveApproval.ts, and template test patterns
  • Add documentation for wait points in workflow service guide and testing guide

@changeset-bot

changeset-bot Bot commented Apr 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0ac30fd

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

This PR includes changesets to release 2 packages
Name Type
@tailor-platform/sdk Minor
@tailor-platform/create-sdk Minor

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

@github-actions

github-actions Bot commented Apr 14, 2026

Copy link
Copy Markdown

⚡ pkg.pr.new

@tailor-platform/sdk

pnpm add https://pkg.pr.new/@tailor-platform/sdk@0ac30fd
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@0ac30fd --help

@tailor-platform/create-sdk

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@0ac30fd
pnpm dlx https://pkg.pr.new/@tailor-platform/create-sdk@0ac30fd my-app

commit: 0ac30fd

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread example/workflows/approval.ts
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from 1b4db4d to 91bde2f Compare April 15, 2026 06:29
@github-actions

This comment has been minimized.

@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from 91bde2f to 9b1e1e6 Compare April 15, 2026 07:52
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

devin-ai-integration[bot]

This comment was marked as outdated.

@toiroakr
toiroakr marked this pull request as ready for review April 16, 2026 07:18
@toiroakr
toiroakr requested a review from remiposo as a code owner April 16, 2026 07:18
@claude

claude Bot commented Apr 16, 2026

Copy link
Copy Markdown

📖 Docs Consistency Check

⚠️ Minor Inconsistencies Found

File Issue Suggested Fix
packages/sdk/docs/services/workflow.md Wait point Payload type description does not clarify top-level null restriction Add explicit note similar to workflow job inputs (line 63)
packages/sdk/docs/services/workflow.md Result type cannot be undefined but this is not documented Add note that Result must be a concrete value (platform constraint)

Details

1. Top-level null restriction for Payload (workflow.md:241)

Current text:

Payload — Data sent when the job suspends (passed to .wait()). Must be a pure JSON value (string, number, boolean, null, arrays, plain objects). Use undefined if no payload is needed.

This lists null as a valid primitive, but the implementation rejects top-level null (wait-point.test.ts:31-36):

// @ts-expect-error - null is not allowed at top level (even in union)
check: define<{ id: string } | null, { ok: boolean }>()

The workflow job input documentation (line 63) handles this clearly:

Top-level null is also rejected because the platform normalizes top-level null/undefined args to {} (nested null inside objects or arrays is preserved).

Recommendation: Add the same clarification to the wait point Payload description.


2. Result cannot be undefined (workflow.md:242)

Current text:

Result — Data returned when the wait point is resolved (returned from .wait(), produced by the .resolve() callback). Must be a pure JSON value.

The implementation explicitly rejects undefined as a Result type (wait-point.test.ts:97-102):

// @ts-expect-error - Result cannot be undefined; platform throws if callback returns undefined
check: define<undefined, undefined>()

Unlike Payload (where undefined alone is allowed as a no-payload convention), Result cannot be undefined at all because the resolve callback must return a concrete value.

Recommendation: Clarify that Result must be a concrete JSON value (cannot be or include undefined), and briefly note this is a platform constraint.


Checked areas

Fully consistent:

  • New APIs documented: defineWaitPoint, defineWaitPoints, WaitPointInstance
  • Test utility documented: setupWaitPointMock with onWait/onResolve handlers
  • Workflow job I/O type changes properly documented (JsonValue-compatible, no Date/toJSON)
  • Examples match implementation (example/workflows/approval.ts, example/resolvers/resolveApproval.ts)
  • Template files follow documented patterns
  • CLAUDE.md updated with wait point usage patterns
  • All example code updated to comply with new type constraints (Date → ISO strings)

These are minor wording issues that could cause confusion, but the core functionality is well-documented and all code examples are correct.


@claude

claude Bot commented Apr 16, 2026

Copy link
Copy Markdown

📖 Docs Consistency Check

✅ No inconsistencies found between documentation and implementation.

Checked areas:

1. Public API Documentation - defineWaitPoints signature, WaitPointInstance interface, type parameters, Jsonify transformation, code examples, multiple wait points usage - all match implementation

2. Testing Documentation - In-memory coordination, setupWaitPointMock signature, return values, cleanup instructions, test examples - all accurate

3. Developer Guide (CLAUDE.md) - Wait point creation, delegation behavior, access patterns - all correct

4. Example Code - approval.ts workflow, resolveApproval.ts resolver, template files, test files - all valid

5. Module Exports - defineWaitPoints and setupWaitPointMock exports and import paths - all correct

Note: Payload must be JSON-compatible is a runtime/platform requirement (not compile-time constraint), which is intentional.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from fe74625 to 3f3c0d9 Compare April 21, 2026 13:56
@github-actions

This comment has been minimized.

@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from 3f3c0d9 to aef4c3e Compare April 21, 2026 14:02
@github-actions

This comment has been minimized.

@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from aef4c3e to c0b5a12 Compare April 21, 2026 14:17
@github-actions

This comment has been minimized.

…eturn type

- createWorkflowJob: Input/Output must be pure JSON (no Date, no toJSON); reject top-level null in Input
- defineWaitPoint / defineWaitPoints: reject top-level null in Payload; Result keeps top-level null
- Replace `..._guard` rest-param guard with `JobBody<I, O>` / `WaitPointDef<P, R>` conditional types (error surfaces at body/define call site)
- example: serialize Date fields to ISO strings in workflow job outputs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@toiroakr
toiroakr force-pushed the feat/workflow-resolve branch from c0b5a12 to fa470ac Compare April 21, 2026 14:29
@github-actions

This comment has been minimized.

devin-ai-integration[bot]

This comment was marked as resolved.

- WaitPointDef now rejects Result = undefined and top-level undefined
  unions in Result. The platform throws
  `resolve: callback must return a value (got undefined)` at runtime,
  so surfacing it at compile time matches the platform's contract.
- getPlatformWorkflow() also verifies that workflow.wait and
  workflow.resolve are functions. Without this, calling
  setupWorkflowMock() without setupWaitPointMock() threw a confusing
  TypeError instead of the intended "Use setupWaitPointMock()" message.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

Previously `setupWorkflowMock` only set `triggerJobFunction`, and `setupWaitPointMock`
only set `wait`/`resolve`. Users had to remember to call both; calling only one and
then hitting the unmocked method produced a confusing TypeError (or silent undefined
for triggerJobFunction).

Each helper now stubs the other's methods with a throw-on-call handler that directs
the user to the missing mock. This removes the need for the extra runtime guard in
`getPlatformWorkflow` (reverted to the simple `!workflow` check) while still surfacing
a clear error when a test forgets a mock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@toiroakr toiroakr assigned remiposo and unassigned toiroakr Apr 22, 2026
Comment on lines +94 to +96
: [Result] extends [undefined]
? "ERROR: Result cannot be undefined (resolve callback must return a value)"
: [undefined] extends [Result]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[q]
I don't quite understand why both need to be checked — is this branching simply to make the error messages clearer?

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.

I originally intended to keep the messages separate, but I decided that much detail wasn't necessary, so I merged them.

* @returns The same object returned by the builder (with correct keys set on each instance)
* @example
* export const waitPoints = defineWaitPoints(define => ({
* /&#42;&#42; Approval for order processing &#42;/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nits]
At least in my environment, &#42 does not render as intended in the IDE.

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.

fixed.

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.

fixed

Comment on lines +12 to +22
/**
* Allowed output types for workflow job body functions.
* Includes Jsonifiable (JSON-serializable values including objects with toJSON like Date),
* undefined, and void.
* Must be JsonValue-compatible, undefined, or void.
*/
export type WorkflowJobOutput = Jsonifiable | undefined | void;
export type WorkflowJobOutput = JsonCompatible<unknown> | undefined | void;

/**
* Convert output type to what trigger returns after JSON serialization.
* - Jsonifiable values are converted via Jsonify (Date -> string, etc.)
* - undefined remains undefined
* - void becomes void
* Input type constraint for workflow jobs.
* Must be JsonValue-compatible or undefined.
*/
type JsonifyOutput<T> = T extends Jsonifiable ? Jsonify<T> : T;
export type WorkflowJobInput = JsonCompatible<unknown> | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nits]
Is this unused?

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.

removed them.

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.

fixed

…SDoc

- Consolidate `[Result] extends [undefined]` and
  `[undefined] extends [Result]` into a single `[undefined] extends [Result]`
  branch. Both cases are errors (unlike Payload, where `Payload = undefined`
  is the no-payload convention), so one check with a combined message suffices.
- Replace the nested `/** */` comment inside a `@example` block (written as
  `/&#42;&#42; &#42;/` HTML entities) with a plain `//` comment. Some IDEs do
  not render the entities, leaving garbled text in autocompletion hover.
- Remove unused `WorkflowJobInput` and `WorkflowJobOutput` type exports from
  `configure/services/workflow/job.ts`; `JobBody` references `JsonCompatible`
  directly and nothing else imports these aliases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@remiposo remiposo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM with 1 nits.

Comment thread packages/sdk/docs/testing.md Outdated
- Use `workflow.mainJob.trigger()` to execute the full workflow chain and get the result
- **Best for:** Testing workflow orchestration and job dependencies

## Testing Wait Points

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nits]
Would it be better for this paragraph to be inside the workflow tests section?

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.

Having three sections—Unit Tests, Workflow Tests, and E2E Tests—felt unnatural, so I have merged Workflow Tests into Unit Tests for now to make it two sections. I plan to revise the specific content of each section separately later.

@github-actions

Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (e9e0568) #987 (90cc524) +/-
Coverage 59.2% 59.3% +0.1%
Code to Test Ratio 1:0.4 1:0.4 +0.0
Details
  |                    | main (e9e0568) | #987 (90cc524) |  +/-  |
  |--------------------|----------------|----------------|-------|
+ | Coverage           |          59.2% |          59.3% | +0.1% |
  |   Files            |            350 |            351 |    +1 |
  |   Lines            |          11894 |          11928 |   +34 |
+ |   Covered          |           7046 |           7083 |   +37 |
+ | Code to Test Ratio |          1:0.4 |          1:0.4 |  +0.0 |
  |   Code             |          77547 |          77802 |  +255 |
+ |   Test             |          31368 |          31474 |  +106 |

Code coverage of files in pull request scope (80.5% → 84.7%)

Files Coverage +/- Status
packages/sdk/src/cli/services/workflow/trigger-transformer.ts 90.0% +0.1% modified
packages/sdk/src/cli/shared/trigger-context.ts 82.0% 0.0% modified
packages/sdk/src/configure/services/workflow/index.ts 0.0% 0.0% modified
packages/sdk/src/configure/services/workflow/job.ts 83.3% +33.3% modified
packages/sdk/src/configure/services/workflow/wait-point.ts 100.0% +100.0% added
packages/sdk/src/utils/brand.ts 100.0% 0.0% modified
packages/sdk/src/utils/test/index.ts 100.0% 0.0% modified
packages/sdk/src/utils/test/mock.ts 53.8% +21.7% modified

SDK Configure Bundle Size

main (e9e0568) #987 (90cc524) +/-
configure-index-size 14.17KB 17.2KB 3.03KB
dependency-chunks-size 33.07KB 33.23KB 0.16KB
total-bundle-size 47.24KB 50.43KB 3.19KB

Runtime Performance

main (e9e0568) #987 (90cc524) +/-
Generate Median 2,585ms 2,653ms 68ms
Generate Max 2,850ms 2,834ms -16ms
Apply Build Median 2,617ms 2,722ms 105ms
Apply Build Max 2,656ms 2,781ms 125ms

Type Performance (instantiations)

main (e9e0568) #987 (90cc524) +/-
tailordb-basic 35,847 35,130 -717
tailordb-optional 3,841 3,841 0
tailordb-relation 7,428 7,428 0
tailordb-validate 2,566 2,566 0
tailordb-hooks 5,767 5,767 0
tailordb-object 12,136 12,136 0
tailordb-enum 2,462 2,462 0
resolver-basic 9,414 9,414 0
resolver-nested 26,101 26,101 0
resolver-array 18,177 18,177 0
executor-schedule 4,234 4,234 0
executor-webhook 873 873 0
executor-record 8,166 8,166 0
executor-resolver 4,368 4,368 0
executor-operation-function 867 867 0
executor-operation-gql 869 869 0
executor-operation-webhook 888 888 0
executor-operation-workflow 2,280 1,714 -566

Reported by octocov

This comment was marked as outdated.

@toiroakr
toiroakr merged commit f0b6b2f into main Apr 24, 2026
59 checks passed
@toiroakr
toiroakr deleted the feat/workflow-resolve branch April 24, 2026 14:09
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.

3 participants