Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/prepare-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# 1. Bumps the selected package's version in its own package.json
# 2. Generates a changelog from git history since the package's last release tag
# 3. Updates the package's changelog file and creates a release branch + package-scoped tag
# Packages are versioned and tagged independently (tag prefixes: v, azuremanaged-v, durable-functions-v).
# Packages are versioned and tagged independently (tag prefixes: v, azureblob-v, azuremanaged-v, durable-functions-v).
# After the workflow completes, manually open a PR from the release branch to main, then publish
# from the package directory using the npm command shown in the run summary (mind the dist-tag).

Expand All @@ -19,6 +19,7 @@ on:
type: choice
options:
- durabletask-js
- durabletask-js-azureblob
- durabletask-js-azuremanaged
- azure-functions-durable
version:
Expand Down Expand Up @@ -60,14 +61,20 @@ jobs:
TAG_PREFIX="azuremanaged-v"
CHANGELOG_PATH="packages/durabletask-js-azuremanaged/CHANGELOG.md"
;;
durabletask-js-azureblob)
PKG_DIR="packages/durabletask-js-azureblob"
NPM_NAME="@microsoft/durabletask-js-azureblob"
TAG_PREFIX="azureblob-v"
CHANGELOG_PATH="packages/durabletask-js-azureblob/CHANGELOG.md"
;;
azure-functions-durable)
PKG_DIR="packages/azure-functions-durable"
NPM_NAME="durable-functions"
TAG_PREFIX="durable-functions-v"
CHANGELOG_PATH="packages/azure-functions-durable/CHANGELOG.md"
;;
*)
echo "::error::Unknown package '$PACKAGE'. Expected one of: durabletask-js, durabletask-js-azuremanaged, azure-functions-durable."
echo "::error::Unknown package '$PACKAGE'. Expected one of: durabletask-js, durabletask-js-azureblob, durabletask-js-azuremanaged, azure-functions-durable."
exit 1
;;
esac
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

### New

- Add opt-in `payloads` storage to gRPC clients/workers, with transparent UTF-8-size-based
externalization and awaited query/history/work-item hydration across orchestration, activity,
and entity V1/V2 payloads. Add the optional `@microsoft/durabletask-js-azureblob` storage package.
- Add .NET-aligned `setLargePayloadAutoPurge()`, reserved replay-safe cleanup tasks, and DTS
lifecycle transport. Storage configuration alone does not enable deletion; cleanup checks
blob ownership and ETags, preserves uncertain outcomes, and leaves legacy v1 blobs untouched.
- Expose readonly `OrchestrationContext.sourceInstanceId` from the existing protobuf
request properties on initial execution and replay, independently of parent identity.
Strings are preserved verbatim; null or missing metadata returns `undefined`.
Expand Down Expand Up @@ -43,6 +49,8 @@

### Fixes

- Keep `PayloadStorageException` cause options structural so its public declarations remain
usable by ES2020 consumers without the global ES2022 `ErrorOptions` type.
- Prevent shared or duplicate pending tasks from stranding `whenAll` orchestrations after all
activities finish. Notify every dependent composite, preserve duplicate result positions,
and detach completed `whenAny` groups from their remaining children.
Expand Down
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,30 @@ const entityResponseBytes = await worker.processEntityBatchRequest(entityBatchRe

`TaskHubGrpcClient` already exposes orchestration start/query/event/terminate/suspend/resume/purge APIs and entity signal/read/query/clean APIs through its existing `hostAddress` and `metadataGenerator` options. Host integrations that need task-hub routing metadata should provide it through `metadataGenerator`, keeping host-specific metadata policy outside the core client. Azure-managed scheduler connection strings remain in `@microsoft/durabletask-js-azuremanaged`.

## Large payloads

Clients and workers can opt into transparent external payload storage with the `payloads`
option. The optional [`@microsoft/durabletask-js-azureblob`](./packages/durabletask-js-azureblob)
package implements .NET-compatible Azure Blob tokens and UTF-8/gzip storage. The default
externalization threshold is 256 KiB (inclusive); the maximum payload size is 10 MiB.
Core users do not acquire Azure Storage dependencies, and disabled behavior is unchanged.

Configure all participating clients and workers before storing externalized payloads.
Azure-managed builders expose `.useExternalizedPayloads({ store })`. Cleanup is separately
opt-in through `client.setLargePayloadAutoPurge(true)` and requires a supporting DTS backend.
See the package's [configuration, interoperability, and cleanup contract](./packages/durabletask-js-azureblob/README.md).

## Long durable timers

`createTimer(Date | seconds)` has no SDK-imposed total-duration cap. Like the Python SDK,
core workers default to three-day backend segments, including durable retry delays. A ten-day
timer uses 3 + 3 + 3 + 1 day segments but remains one logical `TimerTask`.

| Entry point | Timer behavior |
| --- | --- |
| Core `TaskHubGrpcWorker` / `TestOrchestrationWorker` | Three-day default |
| Azure-managed worker builder | Explicitly native timers; DTS supports long timers |
| `durable-functions` worker / `runOrchestrator` | Inherits the core three-day default |
| Entry point | Timer behavior |
| ---------------------------------------------------- | -------------------------------------------------- |
| Core `TaskHubGrpcWorker` / `TestOrchestrationWorker` | Three-day default |
| Azure-managed worker builder | Explicitly native timers; DTS supports long timers |
| `durable-functions` worker / `runOrchestrator` | Inherits the core three-day default |

Core `TaskHubGrpcWorker({ maximumTimerIntervalMs })` and
`TestOrchestrationWorker(backend, { maximumTimerIntervalMs })` accept the Python-equivalent
Expand Down Expand Up @@ -344,13 +357,21 @@ const worker = new TaskHubGrpcWorker({
versioning: { defaultVersion: "v2" },
workItemFilters: "auto",
});
worker.addNamedOrchestrator("Order", async function* (ctx) {
yield ctx.callActivity("Price");
}, "v1");
worker.addNamedOrchestrator("Order", async function* (ctx) {
yield ctx.callActivity("Price");
yield ctx.callSubOrchestrator("Audit");
}, "v2");
worker.addNamedOrchestrator(
"Order",
async function* (ctx) {
yield ctx.callActivity("Price");
},
"v1",
);
worker.addNamedOrchestrator(
"Order",
async function* (ctx) {
yield ctx.callActivity("Price");
yield ctx.callSubOrchestrator("Audit");
},
"v2",
);
worker.addNamedActivity("Price", () => 100, "v1");
worker.addNamedActivity("Price", () => 120, "v2");
worker.addNamedOrchestrator("Audit", () => "audited", "v2");
Expand Down Expand Up @@ -381,11 +402,11 @@ versioned registration for that name disables this fallback: unknown versions fa
existing not-registered error (including the requested version), marked non-retriable. An
unversioned request never selects a versioned implementation.

| Scheduled work | When `options.version` is omitted | Explicit `version: ""` |
| --- | --- | --- |
| Activity | Current orchestration instance's `ctx.version` | Unversioned |
| Sub-orchestration | Worker's `versioning.defaultVersion`, or unversioned if unset | Unversioned |
| Top-level orchestration | Client's `defaultVersion`, or unversioned if unset | Unversioned |
| Scheduled work | When `options.version` is omitted | Explicit `version: ""` |
| ----------------------- | ------------------------------------------------------------- | ---------------------- |
| Activity | Current orchestration instance's `ctx.version` | Unversioned |
| Sub-orchestration | Worker's `versioning.defaultVersion`, or unversioned if unset | Unversioned |
| Top-level orchestration | Client's `defaultVersion`, or unversioned if unset | Unversioned |

An explicit nonempty version overrides these defaults. Activities do **not** inherit the worker
default, and children do **not** inherit the parent's version. Both policy and handler retries
Expand Down Expand Up @@ -431,10 +452,7 @@ Long-running orchestrations can restart with fresh history and optionally move t
orchestration version:

```typescript
const eternalOrchestrator: TOrchestrator = async function* (
ctx: OrchestrationContext,
iteration: number,
): any {
const eternalOrchestrator: TOrchestrator = async function* (ctx: OrchestrationContext, iteration: number): any {
yield ctx.callActivity(processIteration, iteration);
ctx.continueAsNew(iteration + 1, true, "2.0.0");
};
Expand Down
Loading
Loading