diff --git a/packages/durabletask-js/src/worker/runtime-orchestration-context.ts b/packages/durabletask-js/src/worker/runtime-orchestration-context.ts index a14f5e2..6455e96 100644 --- a/packages/durabletask-js/src/worker/runtime-orchestration-context.ts +++ b/packages/durabletask-js/src/worker/runtime-orchestration-context.ts @@ -109,8 +109,7 @@ export class RuntimeOrchestrationContext extends OrchestrationContext { async run(generator: Generator, any, any>) { this._generator = generator; - // TODO: do something with this task - // start the generator + // Start the generator const { value, done } = await this._generator.next(); // if the generator finished, complete the orchestration. @@ -119,12 +118,15 @@ export class RuntimeOrchestrationContext extends OrchestrationContext { return; } - // TODO: check if the task is null? + if (!(value instanceof Task)) { + throw new Error("The orchestrator generator yielded a non-Task object"); + } + this._previousTask = value; // If the yielded task is already complete (e.g., whenAll with an empty array), // resume immediately so the generator can continue. - if (this._previousTask instanceof Task && this._previousTask.isComplete) { + if (this._previousTask.isComplete) { await this.resume(); } } diff --git a/packages/durabletask-js/test/orchestration_executor.spec.ts b/packages/durabletask-js/test/orchestration_executor.spec.ts index c4cd604..e2851b8 100644 --- a/packages/durabletask-js/test/orchestration_executor.spec.ts +++ b/packages/durabletask-js/test/orchestration_executor.spec.ts @@ -1549,6 +1549,69 @@ describe("Orchestration Executor", () => { expect(completeAction?.getResult()?.getValue()).toEqual(JSON.stringify(expectedResult)); }); + it.each([ + { description: "null", yieldedValue: null as any }, + { description: "undefined", yieldedValue: undefined as any }, + ])( + "should fail when orchestrator yields $description as its first value", + async ({ description, yieldedValue }) => { + // An orchestrator that yields a non-Task value as its first yield should fail with a clear error + const badOrchestrator: TOrchestrator = async function* (_ctx: OrchestrationContext): any { + yield yieldedValue; + }; + + const registry = new Registry(); + const name = registry.addOrchestrator(badOrchestrator); + const newEvents = [ + newOrchestratorStartedEvent(), + newExecutionStartedEvent(name, TEST_INSTANCE_ID, undefined), + ]; + const executor = new OrchestrationExecutor(registry, testLogger); + const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents); + const completeAction = getAndValidateSingleCompleteOrchestrationAction(result); + expect(completeAction?.getOrchestrationstatus()).toEqual(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); + expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain("non-Task"); + } + ); + + it("should fail when orchestrator yields a plain object as its first value", async () => { + // An orchestrator that yields a non-Task object should fail with a clear error + const badOrchestrator: TOrchestrator = async function* (_ctx: OrchestrationContext): any { + yield { someProperty: "not a task" }; + }; + + const registry = new Registry(); + const name = registry.addOrchestrator(badOrchestrator); + const newEvents = [ + newOrchestratorStartedEvent(), + newExecutionStartedEvent(name, TEST_INSTANCE_ID, undefined), + ]; + const executor = new OrchestrationExecutor(registry, testLogger); + const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents); + const completeAction = getAndValidateSingleCompleteOrchestrationAction(result); + expect(completeAction?.getOrchestrationstatus()).toEqual(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); + expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain("non-Task"); + }); + + it("should fail when orchestrator yields a primitive as its first value", async () => { + // An orchestrator that yields a primitive (number) instead of a Task should fail + const badOrchestrator: TOrchestrator = async function* (_ctx: OrchestrationContext): any { + yield 42; + }; + + const registry = new Registry(); + const name = registry.addOrchestrator(badOrchestrator); + const newEvents = [ + newOrchestratorStartedEvent(), + newExecutionStartedEvent(name, TEST_INSTANCE_ID, undefined), + ]; + const executor = new OrchestrationExecutor(registry, testLogger); + const result = await executor.execute(TEST_INSTANCE_ID, [], newEvents); + const completeAction = getAndValidateSingleCompleteOrchestrationAction(result); + expect(completeAction?.getOrchestrationstatus()).toEqual(pb.OrchestrationStatus.ORCHESTRATION_STATUS_FAILED); + expect(completeAction?.getFailuredetails()?.getErrormessage()).toContain("non-Task"); + }); + it("should propagate inner whenAll failure to outer whenAny in nested composites", async () => { const hello = (_: any, name: string) => { return `Hello ${name}!`;