All notable changes to this project will be documented in this file.
Breaking changes marked with a π₯
- Update core to fix workflow semaphore not released on cache miss (#424)
- Default
WorkflowHandle
generic T param toWorkflow
(#419)
-
Add comments for unused query and signal generics (#402)
-
[
docs
] Expose worker.CoreOptions (#416) -
[
docs
] Expose BundleOptions and remove__namedParameters
(#404) -
Remove proto usage from workflow runtime (#423)
This is now possible because we're using vm instead of isolated-vm.
- Greatly reduce workflow bundle size - SDK test bundle size went down from 2.77MB to 0.73MB
- Step 1 in supporting custom data converter
- Ignore github actions jobs that require secrets for external committers (#414)
-
[
workflow
] Fix argument wrapping in array when signaling from Workflow (#410)Before this fix, signal arguments sent from a workflow would be wrapped in an array, e.g:
await child.signal(someSignal, 1, '2');
Was received in the child workflow as:
wf.setHandler(someSignal, (num: number, str: string) => { console.log(num, str); // [1, '2'] undefined });
-
[
core
] Upgrade Core to receive fixes to activity heartbeats (#411)-
Fix hang in case Activity completes after heartbeat response indicates Activity timed out.
-
Behavior was incorrect and not inline with the other SDKs. Heartbeats are now throttled using a timer and Core does not count on user to keep sending heartbeats in order flush them out.
Added 2 new
WorkerOption
s to control throttling:maxHeartbeatThrottleInterval
defaultHeartbeatThrottleInterval
-
-
[
docs
] Explain that getHandle doesn't validate workflowId (#400) -
Don't use fs-extra in create-project (#412)
Fixes issue where fs-extra is incompatible with ESM as reported on slack.
- [
worker
] AddWorkerOptions.debugMode
to enable debugging Workflows (#398)
- [
create-project
] Use chalk-template instead of chalk-cli (#396)- Fixes issues where
npx @temporalio/create
fails to resolve thechalk
executable
- Fixes issues where
-
[
core
] Update Core w/ specifying queue kind on polling (#389)- Must be specified for optimization reasons on server
-
[
core
] Update Core to receive bugfixes (#391)- Fix a situation where Core could get stuck polling if WFTs were repeatedly being failed
- Do not fail Workflow if Lang (TypeScript) cancels something that's already completed (e.g. activity, timer, child workflow)
- Fix for Core accidentally still sending commands sometimes for things that were cancelled immediately
-
π₯ [
client
] MakeworkflowId
required (#387)Also remove
WorkflowClientOptions.workflowDefaults
.Reasoning:
- Workflow IDs should represent a meaningful business ID
- Workflow IDs can be used as an idempotency key when starting workflows from an external signal
workflowDefaults
were removed because their presence madetaskQueue
optional inWorkflowOptions
, omitting it from both the defaults and options resulted in runtime errors where we could have caught those at compile time.
Migration:
// Before const client = new WorkflowClient(conn.service, { workflowDefaults: { taskQueue: 'example' } }); const handle = await client.start(myWorkflow, { args: [foo, bar] }); // After const client = new WorkflowClient(conn.service); const handle = await client.start(myWorkflow, { args: [foo, bar], taskQueue: 'example', workflowId: 'a-meaningful-business-id', });
-
Support Windows development (#385)
- Support was added for using and developing the SDK on Windows
- Thanks @cons0l3 and @cretz for the contribution
-
[
workflow
] Use vm instead of isolated-vm (#264)- Removes the
node-gyp
dependency and speeds up installation times - Uses Node's built-in
AsyncLocalStorage
implementation instead of our own - π₯ Requires an additional workflow interceptor if using
@temporalio/interceptors-opentelemetry
import { WorkflowInterceptors } from '@temporalio/workflow'; import { OpenTelemetryInboundInterceptor, OpenTelemetryOutboundInterceptor, OpenTelemetryInternalsInterceptor, } from '@temporalio/interceptors-opentelemetry/lib/workflow'; export const interceptors = (): WorkflowInterceptors => ({ inbound: [new OpenTelemetryInboundInterceptor()], outbound: [new OpenTelemetryOutboundInterceptor()], // Disposes of the internal AsyncLocalStorage used for // the otel workflow context manager. internals: [new OpenTelemetryInternalsInterceptor()], // <-- new });
- Unexpose the
isolatePoolSize
andisolateExecutionTimeout
WorkerOptions
- Removes the
-
Fix type imports (#361)
-
Update core, changes for no more WF update errors (#366)
Failing a Workflow task before this change could put the workflow in a stuck state.
-
π₯ [
workflow
] Throw if patches are used at Workflow top level (#369) -
π₯ [
workflow
] Cancel timer created by condition (#372)Also clean up resources taken by the blocked condition.
This change is incompatible with old Workflow histories. -
π₯ [
workflow
] Ensure signals are always processed (#380)This fixes a critical issue where the SDK was not processing history events in the right order, for example, patches and signals should always be delivered before other events in the context of a single Workflow Task.
This change is incompatible with old Workflow histories.
-
π₯ [
workflow
] Change condition parameter order (#371)// Before const conditionIsTrue = await condition('1s', () => someBooleanVariable); // After const conditionIsTrue = await condition(() => someBooleanVariable, '1s');
-
π₯ Rename ExternalDependencies to Sinks (#370)
-
Support complication on Mac for aarch64-unknown-linux-gnu (#378)
- Add missing index.d.ts to published files in core-bridge package (#347)
- [
docs
] Update algolia index name (#350) - [
core
] Update core to gain infinite poll retries (#355) - [
worker
] Fix Worker possible hang after graceful shutdown period expires (#356)
-
π₯ [
workflow
] RenamecreateActivityHandle
toproxyActivities
(#351) -
The function's usage remains the same, only the name was changed.
Before:
import { createActivityHandle } from '@temporalio/workflow'; import type * as activities from './activities'; const { greet } = createActivityHandle<typeof activities>({ startToCloseTimeout: '1 minute', });
After:
import { proxyActivities } from '@temporalio/workflow'; import type * as activities from './activities'; const { greet } = proxyActivities<typeof activities>({ startToCloseTimeout: '1 minute', });
Reasoning:
- Clarify that the method returns a proxy
- Avoid confusion with
WorkflowHandle
-
π₯ [
workflow
] RenamesetListener
tosetHandler
(#352)BREAKING CHANGE: The function's usage remains the same, only the name was changed.
Before:
import { defineSignal, setListener, condition } from '@temporalio/workflow'; import { unblockSignal } from './definitions'; export const unblockSignal = defineSignal('unblock'); export async function myWorkflow() { let isBlocked = true; setListener(unblockSignal, () => void (isBlocked = false)); await condition(() => !isBlocked); }
After:
import { defineSignal, setHandler, condition } from '@temporalio/workflow'; import { unblockSignal } from './definitions'; export const unblockSignal = defineSignal('unblock'); export async function myWorkflow() { let isBlocked = true; setHandler(unblockSignal, () => void (isBlocked = false)); await condition(() => !isBlocked); }
Reasoning:
- It was our go-to name initially but we decided against it when to avoid confusion with the
WorkflowHandle
concept - Handling seems more accurate about what the function is doing than listening
- With listeners it sounds like you can set multiple listeners, and handler doesn't
- It was our go-to name initially but we decided against it when to avoid confusion with the
-
[
worker
] Add SIGUSR2 to default list of shutdown signals (#346) -
π₯ [
client
] Use failure classes for WorkflowClient errors-
Error handling for
WorkflowClient
andWorkflowHandle
execute
andresult
methods now throwWorkflowFailedError
with the specificTemporalFailure
as the cause. The following error classes were renamed:WorkflowExecutionFailedError
was renamedWorkflowFailedError
.WorkflowExecutionContinuedAsNewError
was renamedWorkflowContinuedAsNewError
.
Before:
try { await WorkflowClient.execute(myWorkflow, { taskQueue: 'example' }); } catch (err) { if (err instanceof WorkflowExecutionFailedError && err.cause instanceof ApplicationFailure) { console.log('Workflow failed'); } else if (err instanceof WorkflowExecutionTimedOutError) { console.log('Workflow timed out'); } else if (err instanceof WorkflowExecutionTerminatedError) { console.log('Workflow terminated'); } else if (err instanceof WorkflowExecutionCancelledError) { console.log('Workflow cancelled'); } }
After:
try { await WorkflowClient.execute(myWorkflow, { taskQueue: 'example' }); } catch (err) { if (err instanceof WorkflowFailedError) { ) { if (err.cause instanceof ApplicationFailure) { console.log('Workflow failed'); } else if (err.cause instanceof TimeoutFailure) { console.log('Workflow timed out'); } else if (err.cause instanceof TerminatedFailure) { console.log('Workflow terminated'); } else if (err.cause instanceof CancelledFailure) { console.log('Workflow cancelled'); } }
-
- Fix and improve opentelemetry interceptors (#340)
- π₯ Make
makeWorkflowExporter
resource param required - Fix Workflow span timestamps
- Disable internal SDK tracing by default
- Connect child workflow traces to their parent
- Connect continueAsNew traces
- Add activity type and workflow type to span names and copy format from Java SDK
- π₯ Some breaking changes were made to the interceptor interfaces
workflowType
input attribute is now consistently calledworkflowType
- Change trace header name for compatibility with Go and Java tracing implementations
- π₯ Make
-
Support bundling Workflow code prior to Worker creation (#336)
-
π₯ Refactor WorkflowHandle creation (#343)
WorkflowClient.start
now returns aWorkflowHandle
WorkflowHandle
no longer hasstart
,signalWithStart
andexecute
methodsWorkflowClient.signalWithStart
was added- To get a handle to an existing Workflow use
WorkflowClient.getHandle
wf.createChildWorklowHandle
was renamed towf.startChild
and immediately starts the Workflowwf.executeChild
replacesChildWorkflowHandle.execute
wf.createExternalWorkflowHandle
was renamed towf.getExternalWorkflowHandle
WorkflowClient - Starting a new Workflow
Before:
const handle = await client.createWorkflowHandle(myWorkflow, { taskQueue: 'q' }); await handle.start(arg1, arg2);
After:
const handle = await client.start(myWorkflow, { taskQueue: 'q', args: [arg1, arg2] });
WorkflowClient - Starting a new Workflow and awaiting completion
Before:
const handle = await client.createWorkflowHandle(myWorkflow, { taskQueue: 'q' }); const result = await handle.execute(arg1, arg2);
After:
const result = await client.execute(myWorkflow, { taskQueue: 'q', args: [arg1, arg2] });
WorkflowClient - signalWithStart
Before:
const handle = await client.createWorkflowHandle(myWorkflow, { taskQueue: 'q' }); await handle.signalWithStart(signalDef, [signalArg1, signalArg2], [wfArg1, wfArg2]);
After:
await client.signalWithStart(myWorkflow, { args: [wfArg1, wfArg2], taskQueue: 'q', signal: signalDef, signalArgs: [signalArg1, signalArg2], });
WorkflowClient - Get handle to an existing Workflow
Before:
const handle = await client.createWorkflowHandle({ workflowId });
After:
const handle = await client.getHandle(workflowId);
@temporalio/workflow
- Start Child WorkflowBefore:
const handle = await workflow.createChildWorkflowHandle(myWorkflow, { taskQueue: 'q' }); await handle.start(arg1, arg2);
After:
const handle = await workflow.startChild(myWorkflow, { taskQueue: 'q', args: [arg1, arg2] });
@temporalio/workflow
- Start Child Workflow and await completionBefore:
const handle = await workflow.createChildWorkflowHandle(myWorkflow, { taskQueue: 'q' }); const result = await handle.execute(arg1, arg2);
After:
const result = await workflow.executeChild(myWorkflow, { taskQueue: 'q', args: [arg1, arg2] });
@temporalio/workflow
- Get handle to an external WorkflowBefore:
const handle = await workflow.createExternalWorkflowHandle(workflowId);
After:
const handle = await workflow.getExternalWorkflowHandle(workflowId);
- Update docker-compose server version to 1.13.0 (#338)
- [
workflow
] Validate timer duration is positive (#328) - [
worker
] Provide better error messages when instantiating rust Core (#331)
-
π₯ Restructure code in prep for vm transition (#317)
- Decrease Workflow bundle size from ~7.44MB to ~2.75MB
- π₯ Remove otel module from @temporalio/common default export
- Rename WorkflowIsolateBuilder to WorkflowCodeBundler and remove unused methods
- Add Workflow and WorkflowCreator interfaces to support pluggable workflow environments (prepare for VM)
- π₯ Simplify external dependencies mechanism to only support void functions and remove the isolated-vm transfer options.
-
Support
ms
formatted string for activity.Context.sleep (#322) -
π₯ Runtime determinism tweaks (#326)
- Undelete WeakMap and WeakSet
- Delete FinalizationRegistry
- Print more useful information in load test (#315)
- [
proto
] Remove core-bridge dependency from proto package (#295) - Indefinitely reconnect to server on poll errors (#298)
- WorkflowHandle.signal() can take a string, default args to [] (#297)
- Poll for Activities even if none registered (#300)
- Delay query processing until workflow has started (#301)
- Shutdown native worker on webpack errors and provide better error message (#302)
- Support ES Module based projects (#303)
- Add more links in per-package READMEs for NPM (#296)
- Add nightly "load sampler" run (#281)
- Add smorgasbord workflow
- Address smorgasboard wf feedback
- Test init from fetch-esm sample
- [
workflow
] Export ChildWorkflowOptions and ParentClosePolicy - Don't set default workflowIdReusePolicy
- Allow getting Date in Workflow top level
-
[
client
] Add gRPC retry interceptors -
Enhance
@temporalio/create
and use samples-node as its source (#273) -
π₯[
core
] ChangeWARNING
log level toWARN
-
Add Core option to forward logs from Rust to configurable Node logger
-
[
workflow
] Supportms
formatted strings in sleep() function -
π₯[
worker
] RemoveworkDir
Worker optionActivities and Workflows are not automatically detected anymore.
nodeModulesPath
has been renamednodeModulesPaths
to support resolution from multiplenode_modules
paths, the Worker will attempt to autodiscovernode_modules
based on providedworkflowsPath
. -
[
workflow
] Provide better error message when calling createChildWorkflowHandle on unregistered workflow -
π₯[
client
] Switch parameter order in WorkflowClient.execute and start methods -
[
workflow
] Add condition helper function -
Link Node / Core and
interceptors/opentelemetry
generated spans together -
π₯[
workflow
] Implement Workflow API 3rd revision (#292)All existing Workflows need to be rewritten in the new form:
import * as wf from '@temporalio/workflow'; export const unblockSignal = wf.defineSignal('unblock'); export const isBlockedQuery = wf.defineQuery<boolean>('isBlocked'); export async function myWorkflow(arg1: number, arg2: string): Promise<void> { let isBlocked = true; wf.setListener(unblockSignal, () => void (isBlocked = false)); wf.setListener(isBlockedQuery, () => isBlocked); await wf.condition(() => !isBlocked); }
See the proposal for more information.
- Remove port bindings from buildkite docker compose file
- Remove unneeded bit of protection around shutdown now that core handles it
- Reuse loaded package.json for ServerOptions.sdkVersion
- Initial nightly long run implementation
- Print error with traceback when promise hook fails
- Pass client/version info to core, work with new options builders
- Properly set otel context when calling into core
- Handle scenario where worker is totally removed before calling next poll
- Don't log empty metadata
- Fix double heartbeat() docstring