Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This guide covers the Durable Functions-specific changes needed when upgrading to the v4 Node.js programming model. To create a new v4 app instead, see the quickstarts for JavaScript and TypeScript.
Important
Complete the general Node.js v4 upgrade guide first. This article covers only the extra Durable Functions-specific changes.
Migration checklist
Use the following checklist to track your progress through each migration step:
| Step | Section |
|---|---|
| 1. Verify prerequisites | Prerequisites |
| 2. Upgrade the npm package | Upgrade the durable-functions npm package |
3. Replace function.json with code-based registration |
Register your Durable Functions triggers |
| 4. Register durable client in code | Register your Durable Client input binding |
| 5. Update client API calls | Update your Durable Client API calls |
6. Update callHttp calls |
Update calls to the callHttp API |
| 7. (TypeScript) Use new exported types | Use new exported types for stronger type safety |
Prerequisites
- Node.js version 18.x+
- TypeScript version 4.x+
- Azure Functions Runtime version 4.25+
- Azure Functions Core Tools version 4.0.5382+ (if running locally)
Upgrade the durable-functions npm package
The programming model version and the durable-functions package version are different:
| Programming model | durable-functions package |
|---|---|
| v3 | 2.x |
| v4 | 3.x |
Upgrade to the v3.x package:
npm install durable-functions
Register your Durable Functions triggers
In the v4 model, you no longer declare triggers and bindings in a separate function.json file. Instead, register your Durable Functions triggers directly in code using the df.app namespace. After migrating each function, delete its function.json file.
| Function type | Registration method |
|---|---|
| Orchestration | df.app.orchestration() |
| Entity | df.app.entity() |
| Activity | df.app.activity() |
The following examples show the migration pattern for each function type.
Orchestration
const df = require('durable-functions');
const activityName = 'helloActivity';
df.app.orchestration('durableOrchestrator', function* (context) {
const outputs = [];
outputs.push(yield context.df.callActivity(activityName, 'Tokyo'));
outputs.push(yield context.df.callActivity(activityName, 'Seattle'));
outputs.push(yield context.df.callActivity(activityName, 'Cairo'));
return outputs;
});
import * as df from 'durable-functions';
import { OrchestrationContext, OrchestrationHandler } from 'durable-functions';
const activityName = 'hello';
const durableHello1Orchestrator: OrchestrationHandler = function* (context: OrchestrationContext) {
const outputs = [];
outputs.push(yield context.df.callActivity(activityName, 'Tokyo'));
outputs.push(yield context.df.callActivity(activityName, 'Seattle'));
outputs.push(yield context.df.callActivity(activityName, 'Cairo'));
return outputs;
};
df.app.orchestration('durableOrchestrator', durableHello1Orchestrator);
Entity
const df = require('durable-functions');
df.app.entity('Counter', (context) => {
const currentValue = context.df.getState(() => 0);
switch (context.df.operationName) {
case 'add':
const amount = context.df.getInput();
context.df.setState(currentValue + amount);
break;
case 'reset':
context.df.setState(0);
break;
case 'get':
context.df.return(currentValue);
break;
}
});
import * as df from 'durable-functions';
import { EntityContext, EntityHandler } from 'durable-functions';
const counterEntity: EntityHandler<number> = (context: EntityContext<number>) => {
const currentValue: number = context.df.getState(() => 0);
switch (context.df.operationName) {
case 'add':
const amount: number = context.df.getInput();
context.df.setState(currentValue + amount);
break;
case 'reset':
context.df.setState(0);
break;
case 'get':
context.df.return(currentValue);
break;
}
};
df.app.entity('Counter', counterEntity);
Activity
Register your Durable Client input binding
In the v4 model, registering secondary input bindings like durable clients is also done in code. Use input.durableClient() to register a durable client input binding, then use getClient() to retrieve the client instance. The following example uses an HTTP triggered function.
const { app } = require('@azure/functions');
const df = require('durable-functions');
app.http('durableHttpStart', {
route: 'orchestrators/{orchestratorName}',
extraInputs: [df.input.durableClient()],
handler: async (_request, context) => {
const client = df.getClient(context);
// Use client in function body
},
});
import { app, HttpHandler, HttpRequest, HttpResponse, InvocationContext } from '@azure/functions';
import * as df from 'durable-functions';
const durableHttpStart: HttpHandler = async (request: HttpRequest, context: InvocationContext): Promise<HttpResponse> => {
const client = df.getClient(context);
// Use client in function body
};
app.http('durableHttpStart', {
route: 'orchestrators/{orchestratorName}',
extraInputs: [df.input.durableClient()],
handler: durableHttpStart,
});
Update your Durable Client API calls
Several APIs on DurableClient (renamed from DurableOrchestrationClient) now accept a single options object instead of multiple optional arguments. The most commonly affected APIs are startNew and getStatus; if you only use those APIs, you can skip the rest of the table. The following example shows the updated pattern:
The following table lists all the affected APIs:
| V3 model (durable-functions v2.x) | V4 model (durable-functions v3.x) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Update calls to the callHttp API
In v3.x of durable-functions, the callHttp() API was updated with the following changes:
| Change | Details |
|---|---|
| Arguments → options object | All arguments are now passed as a single options object, similar to Express. |
uri → url |
Renamed for consistency. |
content → body |
Renamed for consistency. |
asynchronousPatternEnabled → enablePolling |
Renamed for clarity. |
If your orchestrations use callHttp, update the calls to the new syntax:
Use new exported types for stronger type safety
The durable-functions package now exposes new types that weren't previously exported. These types allow you to more strongly type your functions and provide stronger type safety for your orchestrations, entities, and activities. They also improve IntelliSense for authoring these functions.
The following list includes some of the new exported types:
OrchestrationHandler, andOrchestrationContextfor orchestrationsEntityHandlerandEntityContextfor entitiesActivityHandlerfor activitiesDurableClientclass for client functions
Troubleshooting
"The orchestrator can’t execute without an OrchestratorStarted event"
If you see the following error, make sure you're running at least v4.25 of the Azure Functions Runtime or at least v4.0.5382 of Azure Functions Core Tools if running locally.
Exception: The orchestrator can not execute without an OrchestratorStarted event.
Stack: TypeError: The orchestrator can not execute without an OrchestratorStarted event.
Functions not discovered after migration
If your functions aren't appearing after migration, verify that:
- You deleted or renamed the old
function.jsonfiles. Leftoverfunction.jsonfiles can conflict with code-based registrations. - Your
df.app.orchestration(),df.app.entity(), anddf.app.activity()calls are being executed at startup (for example, in a file imported by your main entry point).
Other issues
For other issues, file a bug report in the azure-functions-durable-js GitHub repo.