Skip to content

fix(durable-functions): route sync single-arg orchestrators to core-native (#321)#323

Merged
YunchuWang merged 5 commits into
mainfrom
yunchuwang-sync-single-arg-core-native
Jul 23, 2026
Merged

fix(durable-functions): route sync single-arg orchestrators to core-native (#321)#323
YunchuWang merged 5 commits into
mainfrom
yunchuwang-sync-single-arg-core-native

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Fixes #321.

What

In the compat durable-functions (v4) package, a plain synchronous, single-argument, non-generator orchestrator (ctx) => value is now classified as core-native: it passes through wrapOrchestrator unchanged and receives the core OrchestrationContext directly.

Previously the classifier fell back to arity for plain sync functions — a lone context parameter was treated as classic and handed the { df, log } context. That mis-routed core-native handlers like (ctx) => ctx.instanceId: ctx.instanceId was undefined and members such as ctx.callActivity / ctx.newGuid() threw. That is the bug reported in #321.

Change (minimal)

  • src/orchestration-context.tsisClassicOrchestrator: the plain-sync branch now returns false (core-native) instead of return handler.length <= 1. Only a sync generator (function*) is classic. Updated the wrapOrchestrator doc comment to match.
  • test/unit/orchestration-context.spec.ts — the "replay-safe logger on the classic context" test used a plain non-generator classic orchestrator; converted it to the standard function* form (same intent). Added a [durable-functions v4] isClassicOrchestrator can't distinguish a plain sync single-arg core-native orchestrator from a classic one #321 regression test asserting the sync single-arg native handler passes through unchanged (identity).
  • README.md / CHANGELOG.md — documented the breaking change.

Behavior matrix

Orchestrator shape Before After
function*(ctx) classic (uses context.df.*) classic classic (unchanged)
async function*(ctx) core-native core-native (unchanged)
async (ctx) => … core-native core-native (unchanged)
(ctx, input) => … (arity 2) core-native core-native (unchanged)
(ctx) => … sync single-arg classic (arity fallback) core-native (fixes #321)

⚠️ Breaking change

A classic v3 orchestrator written as a plain non-generator function using context.df.* in this exact shape (sync, single-arg, non-generator) no longer receives the classic context. Convert it to the standard classic generator form (function*, unaffected) or to the core-native ctx.* API. Standard function* classic orchestrators — the normal v3 shape — are unaffected. Documented under "Migrating from durable-functions v3" in the package README and in the CHANGELOG.

Validation

  • npm run build:core → exit 0
  • npm run build -w durable-functions → exit 0
  • Full compat unit suite: 15 suites / 103 tests passing

…ative (#321)

A plain sync, single-argument, non-generator orchestrator (ctx) => value
is now treated as core-native and receives the core OrchestrationContext
directly, fixing #321. Detection no longer falls back to arity for plain
sync functions. BREAKING: a classic v3 orchestrator written as a plain
non-generator using context.df.* is no longer supported; use a function*
generator (unaffected) or the core-native ctx.* API. Documented in the
package README and CHANGELOG.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05
Copilot AI review requested due to automatic review settings July 23, 2026 16:25

Copilot AI 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.

Pull request overview

This PR fixes durable-functions (v4 compat) orchestrator classification so a plain synchronous, single-argument, non-generator handler (ctx) => value is treated as core-native and receives the core OrchestrationContext (fixing #321), rather than being misrouted to the classic { df, log } context via an arity fallback.

Changes:

  • Updated isClassicOrchestrator to classify only sync generators (function*) as classic; plain sync functions now always pass through as core-native.
  • Updated unit tests to reflect the classic orchestrator “generator” ground truth and added a regression test for #321 asserting identity passthrough for (ctx) => ....
  • Documented the resulting breaking change in the package README and CHANGELOG.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
packages/azure-functions-durable/src/orchestration-context.ts Removes arity fallback for plain sync functions; only function* is treated as classic.
packages/azure-functions-durable/test/unit/orchestration-context.spec.ts Updates classic logger test to use function* and adds #321 regression test for sync single-arg core-native passthrough.
packages/azure-functions-durable/README.md Documents breaking change for plain non-generator “classic” orchestrators.
packages/azure-functions-durable/CHANGELOG.md Records behavior change and breaking-change guidance for consumers.

…only (#321)

Zero-arg plain sync orchestrators keep their prior classic classification;
only the sync single-argument (ctx) => value shape is routed to core-native.
Behavior change is now provably limited to the single-arg case.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05
Copilot AI review requested due to automatic review settings July 23, 2026 16:38

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread packages/azure-functions-durable/src/orchestration-context.ts Outdated
…sync non-generators (#321)

A classic v3 orchestrator must be a generator (it has to yield durable
tasks), so no plain sync non-generator can be a legitimate classic
orchestrator regardless of arity. Route all plain sync non-generators to
core-native (return false) rather than special-casing zero-arg. The only
real orchestrator shape whose behavior changes remains sync single-arg
(ctx) => value; zero-arg is degenerate and behaves identically either way.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05
Copilot AI review requested due to automatic review settings July 23, 2026 16:44

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

…test (#321)

The classic replay-safe-logger test uses a `function*` that intentionally
never yields — it logs and returns immediately to exercise the classic log
wiring, and the test asserts completion on the first `.next()`. ESLint
`require-yield` flags empty generators, so disable that rule on the line,
matching existing repo usage (e.g. parent-orchestration-instance.spec.ts).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05
Copilot AI review requested due to automatic review settings July 23, 2026 18:01

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Restore packages/azure-functions-durable/CHANGELOG.md to its main-branch
state. CHANGELOG entries are finalized at release time; the breaking-change
note remains documented in the package README's "Migrating from
durable-functions v3" section, which is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d86f5f69-4e1a-4c1d-b017-5e290c85cc05
Copilot AI review requested due to automatic review settings July 23, 2026 18:09

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

packages/azure-functions-durable/src/orchestration-context.ts:224

  • After this change, plain sync non-generator handlers are always core-native, so a classic orchestrator must be a function* generator. However, the exported ClassicOrchestrator type still allows a classic handler to be a plain non-generator function (it includes | unknown), which no longer matches runtime behavior and may mislead TS consumers.
 *   the wrapper delegates to it via `yield*`.
 * - A plain SYNC (non-generator) function is CORE-NATIVE regardless of arity and passes through
 *   unchanged, receiving the core {@link OrchestrationContext}. This fixes #321 (a native
 *   `(ctx) => ctx.instanceId` previously mis-routed by arity to the classic context). BREAKING: a
 *   classic v3 orchestrator written as a plain non-generator using `context.df.*` is no longer

Comment thread packages/azure-functions-durable/src/orchestration-context.ts
@YunchuWang
YunchuWang merged commit 592d441 into main Jul 23, 2026
30 checks passed
@YunchuWang
YunchuWang deleted the yunchuwang-sync-single-arg-core-native branch July 23, 2026 19:00
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.

[durable-functions v4] isClassicOrchestrator can't distinguish a plain sync single-arg core-native orchestrator from a classic one

3 participants