Azure / Azure/azure-functions-durable-js
weak typing for yield results on `callActivity`
- 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
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