Azure / Azure/azure-functions-durable-js

weak typing for yield results on `callActivity`

Open
#524 1 comment 0 reactions 0 assignees View on GitHub
Enhancement P3
Dominant language
TypeScript
Stars
142
Forks
66
Avg merge
3d 19h
Merged PRs (30d)
4

Description

**Describe the bug**

The choice to use Generators for Durable Functions in Azure was deliberate as I understand it, but it _has_ made strongly typing the APIs for Orchestrations and Activities very challenging. With custom Promise implementations the typing is pretty straightforward, but the replay-ability is probably harder?

I _don't think_ Typescript supports stronger typing of the types on a per-yield result basis (https://github.com/microsoft/TypeScript/issues/32523). Is there a plan for this that will make this process way less painful? I'm usually pretty fantastic at hacking around stuff like this with typescript, but I can't figure out how in this scenario (specifically for activity return types).

**Investigative information**

- Durable Functions extension version: ? Installed automagically by tooling. No clue. ?,
- durable-functions npm module version: `"durable-functions": "^3.0.0-alpha.5"
- Language (JavaScript/TypeScript) and version: Typescript ^5.1.3
- Node.js version: node v18.12.1

***If deployed to Azure App Service***

Local only at this time.

> If you don't want to share your Function App name or Functions names on GitHub, please be sure to provide your Invocation ID, Timestamp, and Region - we can use this to look up your Function App/Function. Provide an invocation id per Function. See the [Functions Host wiki](https://github.com/Azure/azure-webjobs-sdk-script/wiki/Sharing-Your-Function-App-name-privately) for more details.

**To Reproduce**

Check out the `ActivityCaller` type below. VS Code complains (with good reason) that the `Output` parameter is unused:

```ts
import type { FunctionInput, FunctionOutput, InvocationContext } from "@azure/functions";
import type { OrchestrationContext, ActivityHandler, RetryOptions } from "durable-functions";

import { app } from "durable-functions";

type ActivityExtraOptions = Partial<{
extraInputs: FunctionInput[];
extraOutputs: FunctionOutput[];
}>;

type CallOptions = Partial<{
retry: RetryOptions;
}>;

type Handler = (input: Input, ctx: InvocationContext) => Promise;

// LOOK HERE: I cannot type this in any meaningful way that results in a type I can manipulate into
// const out: Output = yield callThisFunction(ctx, "input");
type ActivityCaller = (ctx: OrchestrationContext, input: Input, { retry }?: CallOptions) => Task;

export function createActivity(name: string, handler: Handler): ActivityCaller;
export function createActivity(name: string, options: ActivityExtraOptions, handler: Handler): ActivityCaller;
export function createActivity(
name: string,
handlerOrOptions: ActivityHandler | ActivityExtraOptions,
maybeHandler?: ActivityHandler,
): ActivityCaller {
let options = typeof handlerOrOptions === "function" ? {} : handlerOrOptions;
let handler = typeof handlerOrOptions === "function" ? handlerOrOptions : maybeHandler;
if (handler == null) {
throw new Error("Options were passed, but no handler was passed?");
}

app.activity(name, {
...options,
handler,
});

return Object.defineProperty(
function (ctx: OrchestrationContext, input: Input, { retry }: CallOptions = {}) {
if (retry) {
return ctx.df.callActivityWithRetry(name, retry, input) as Output;
} else {
return ctx.df.callActivity(name, input) as Output;
}
},
"name",
{ value: name }
);
}

```

**Expected behavior**

There is _some_ way other than `as` or type assertions in the orchestrator to bind the return type of a called function to the Task result type.

**Actual behavior**

There is _not_ such a technique that I know of.

**Screenshots**

N/A

**Known workarounds**

`as` or manually specifying this stuff.

**Additional context**

N/A

Contributor guide

Open the contributing guide

Research direction

Start with the reported ActivityCaller and createActivity declarations, then inspect how ctx.df.callActivity and callActivityWithRetry expose Task results. Compare the desired per-activity yield typing with the TypeScript limitation linked in the issue; done means an orchestrator can receive the activity return type without assertions.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, node.js, typescript
Domain
backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.