All notable changes to this project will be documented in this file.
Breaking changes marked with a π₯
-
Reverted (#430) which added protobuf payload converters
This broke Workflows for some users who had the
assert
package installed in theirnode_modules
folder.proto3-json-serializer
(added in this PR) requiresassert
which transitively requiresutils
which relies onprocess
being available.
-
Fix Workflow retryable
ApplicationFailure
fails execution (#432)- Makes
ApplicationFailure.retryable
fail the workflow execution and not the task as intended, this was wrongly implemented in #429.
- Makes
-
Update core submodule to receive recent bugfixes (#433)
- Fix WFT failures sometimes getting stuck in a spam loop (sdk-core#240)
- Move warning message for failed activations to be only during reports (sdk-core#242)
- Fix double-application of an empty WFT when handling legacy queries (sdk-core#244)
-
Add Protobuf binary and JSON data converter and WorkerOptions.dataConverterPath (#430)
- Renamed
WorkerOptions.dataConverter
toWorkerOptions.dataConverterPath
: path to a module withdataConverter
named export. This is needed in order to get the run the data converter in the node worker thread. - Added
ProtobufBinaryDataConverter
ProtobufJsonDataConverter
that convert protobufjs JSON modules based arguments and return types to and from Payloads.
IMPORTANT:
Workflow cannot deserialize protobuf messages yet as it still uses the default data converter - the ability to customize the workflow data converter is coming soon.
Design notes:
https://github.com/temporalio/sdk-typescript/blob/main/docs/protobuf-libraries.md
Other notes:
- Other SDKs, can read protobuf payloads generated by the TypeScript SDK
- Other SDKs, when protobuf-serializing, must include the name of the class in
payload.metadata.messageType
for the TS SDK to read- This has not been implmented yet
- Will later be used by the UI
- Renamed
-
Use bundled Workflow interceptors (#427)
Addresses issue #390 where workflow interceptor modules might not be present when using pre-bundled workflow code.
-
π₯ Add validation to retry policy and use a TS friendly interface everywhere (#426)
RetryOptions
was renamedRetryPolicy
- client
WorkflowOptions
no longer accepts protobufretryPolicy
instead it has a TSRetryPolicy
retry
attribute
-
Implement async Activity completion (#428)
- Activity can throw
CompleteAsyncError
to ask the worker to forget about it - Later on the
AsyncCompletionClient
can be used to complete that activity
- Activity can throw
-
[
workflow
] Handle unhandled rejections in workflow code (#415)- Associate unhandled rejections from workflow code to a specific runId.
- Makes the unhandled rejection behavior consistent between node 14 and 16 and propagates failure back to the user. Previously, in node 16 the process would crash and in node 14 we would incorrectly ignore rejections leading to unexpected workflow behavior.
-
π₯ [
workflow
] Make random workflow errors retryable (#429)BREAKING CHANGE: Before this change throwing an error in a Workflow would cause the Workflow execution to fail. After the change only the Workflow task fails on random errors. To fail the Workflow exection throw
ApplicationFailure.nonRetryable
.To make other error types non retryable use the
WorkflowInboundCallsInterceptor
execute
andhandleSignal
methods to catch errors thrown from the Workflow and convert them to non retryable failures, e.g:class WorkflowErrorInterceptor implements WorkflowInboundCallsInterceptor { async execute( input: WorkflowExecuteInput, next: Next<WorkflowInboundCallsInterceptor, 'execute'> ): Promise<unknown> { try { return await next(input); } catch (err) { if (err instanceof MySpecialNonRetryableError) { throw ApplicationFailure.nonRetryable(err.message, 'MySpecialNonRetryableError'); } throw err; } } }
NOTE: Propagated Activity and child Workflow failures are considered non retryable and will fail the workflow execution.
- 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