Skip to content

feat(worker): add perActivityOptions to declareWorkflow#125

Open
btravers wants to merge 2 commits intomainfrom
feat/per-activity-options
Open

feat(worker): add perActivityOptions to declareWorkflow#125
btravers wants to merge 2 commits intomainfrom
feat/per-activity-options

Conversation

@btravers
Copy link
Copy Markdown
Owner

@btravers btravers commented Mar 5, 2026

Summary

  • Adds a new optional perActivityOptions field to declareWorkflow that allows specifying ActivityOptions per activity
  • Options are merged with activityOptions (per-activity values take precedence)
  • activityOptions is now optional — you can rely solely on perActivityOptions if you prefer

Usage

export const processOrder = declareWorkflow({
  workflowName: 'processOrder',
  contract: myContract,
  activityOptions: { startToCloseTimeout: '1 minute' },
  perActivityOptions: {
    chargePayment: { startToCloseTimeout: '30 seconds', retry: { maximumAttempts: 1 } },
    sendEmail: { startToCloseTimeout: '5 minutes' },
  },
  implementation: async (context, args) => { ... },
});

Test plan

  • Typecheck passes (pnpm typecheck)
  • All unit tests pass (pnpm test)
  • Added workflowWithPerActivityOptions test workflow demonstrating the feature
  • Changeset added for minor version bump

Closes #122

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 5, 2026 11:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support in @temporal-contract/worker for specifying ActivityOptions per activity when declaring workflows, with per-activity values overriding the workflow defaults.

Changes:

  • Extend declareWorkflow with optional perActivityOptions and make activityOptions optional.
  • Build activity proxies with either default options or per-activity merged options.
  • Add a changeset and a new test workflow intended to demonstrate the feature.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/worker/src/workflow.ts Adds perActivityOptions support and makes activityOptions optional when creating activity proxies.
packages/worker/src/tests/test.workflows.ts Adds a new workflow export intended to use per-activity overrides.
.changeset/per-activity-options.md Documents the new option and bumps @temporal-contract/worker minor version.

Comment on lines +189 to +205
const rawActivities: Record<string, (...args: unknown[]) => Promise<unknown>> = {};
for (const activityName of allActivityNames) {
const specificOptions = perActivityOptionsMap?.[activityName];
if (specificOptions !== undefined) {
const proxy = proxyActivities<Record<string, (...args: unknown[]) => Promise<unknown>>>({
...activityOptions,
...specificOptions,
});
rawActivities[activityName] = proxy[activityName] as (
...args: unknown[]
) => Promise<unknown>;
} else if (defaultProxy !== null) {
rawActivities[activityName] = defaultProxy[activityName] as (
...args: unknown[]
) => Promise<unknown>;
}
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When activityOptions is undefined and perActivityOptions doesn’t include an entry for every activity in the contract/definition, this loop simply omits those activities from rawActivities. That later triggers createValidatedActivities to throw “Activity implementation not found…”, which is misleading (the implementation exists; the proxy wasn’t created due to missing options). Consider explicitly detecting this case here and throwing a clearer error listing which activity names are missing options (or otherwise ensure a proxy is created for every activity).

Copilot uses AI. Check for mistakes.
Comment on lines 173 to +205
if (definition.activities || contract.activities) {
const rawActivities =
proxyActivities<Record<string, (...args: unknown[]) => Promise<unknown>>>(activityOptions);
const allActivityNames = [
...Object.keys(definition.activities ?? {}),
...Object.keys(contract.activities ?? {}),
];

const defaultProxy =
activityOptions !== undefined
? proxyActivities<Record<string, (...args: unknown[]) => Promise<unknown>>>(
activityOptions,
)
: null;

const perActivityOptionsMap = perActivityOptions as
| Record<string, ActivityOptions | undefined>
| undefined;
const rawActivities: Record<string, (...args: unknown[]) => Promise<unknown>> = {};
for (const activityName of allActivityNames) {
const specificOptions = perActivityOptionsMap?.[activityName];
if (specificOptions !== undefined) {
const proxy = proxyActivities<Record<string, (...args: unknown[]) => Promise<unknown>>>({
...activityOptions,
...specificOptions,
});
rawActivities[activityName] = proxy[activityName] as (
...args: unknown[]
) => Promise<unknown>;
} else if (defaultProxy !== null) {
rawActivities[activityName] = defaultProxy[activityName] as (
...args: unknown[]
) => Promise<unknown>;
}
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds perActivityOptions, but there’s no test that actually executes a workflow using per-activity overrides and asserts the behavior. The new test workflow in test.workflows.ts isn’t referenced by the integration tests, so the feature is currently unexercised. Please add/adjust an integration test to run a workflow that uses perActivityOptions (e.g., with activityOptions omitted and per-activity options supplied for all activities) to ensure it works end-to-end.

Copilot uses AI. Check for mistakes.

// Workflow that uses per-activity options
export const workflowWithPerActivityOptions = declareWorkflow({
workflowName: "workflowWithActivities",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflowWithPerActivityOptions sets workflowName: "workflowWithActivities", which duplicates the existing workflow and means starting workflowWithPerActivityOptions by name won’t run this implementation (Temporal resolves workflow type by exported symbol name). This makes the new workflow a confusing duplicate and it doesn’t actually demonstrate per-activity options in tests. Consider either updating the existing workflowWithActivities export to include perActivityOptions, or add a new workflow definition to testContract and use a matching workflowName + integration test.

Suggested change
workflowName: "workflowWithActivities",
workflowName: "workflowWithPerActivityOptions",

Copilot uses AI. Check for mistakes.
btravers and others added 2 commits March 5, 2026 19:24
Allow specifying ActivityOptions per activity in declareWorkflow via the
new optional perActivityOptions field. Options are merged with the default
activityOptions (per-activity values take precedence), enabling fine-grained
control over timeouts and retry policies per activity.

Closes #122

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@btravers btravers force-pushed the feat/per-activity-options branch from 73c0a4f to edea609 Compare March 5, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to specify ActivityOptions per activity

2 participants