Releases: temporalio/sdk-typescript
v1.9.1
Bug Fixes
- [
worker
] Fix prometheus and otel regressions (#1345) - Fix
devDependencies
in some of the SDK packages where file URLs were used instead of version numbers.
Documentation
- Add note on custom conveters and bundleWorkflowCode (#1338)
- Fix sort order + snipsync examples issue (#1341)
- [
worker
] Update webpack error doc link in worker/bundler.ts (#1344) - Explain the purpose of
updateId
(#1318)
Features
- [
worker
] Add a workflow metadata query (#1319)
v1.9.0
Important updates
-
The
reuseV8Context
worker option is now enabled by default. This major optimization of the Workflow sandboxing runtime significantly reduces RAM and CPU usage, without compromising the safety of the deterministic sandbox. You may still revert to the legacy execution model by addingreuseV8Context: false
to your worker options.Note that support for the legacy, non-
reuseV8Context
execution model may be removed at some point in the future. Please reach out if you find any issue that requires disablingreuseV8Context
. -
Support for Node.js 14 and Node.js 16 will soon be dropped. Node.js 14 officially reached end-of-life on April 30th, 2023, and Node.js 16 reached end-of-life on September 11th, 2023 (see Node.js release schedule).
This has since then caused a rippling effect, as more and more libraries in the NPM ecosystem are progressively dropping support for both of those older targets. Given that challenges to maintaining compatibility with these outdated Node.js releases will only continue to grow, it is evident that we will soon need to follow suit in discontinuing support for Node.js 14 and 16 in the Temporal TypeScript SDK.
For those reasons:
- TS SDK v1.9 will be the last minor release to support Node.js 14.
- TS SDK v1.10 could possibly be the last minor release to support Node.js 16. Note that this also implies that GLIBC 2.27 will no longer be supported from that point on, since Node.js 18+ won’t compile on GLIBC older than 2.28.
We strongly encourage everyone to take immediate action in updating their deployments to supported versions of Node.js to ensure continued compatibility and receive the latest features and security updates.
New & Noteworthy
Support for Workflow Update (experimental)
-
Introduced Workflow Update APIs, both on Workflow side and Client side (#1277, #1312 and #1320). Kudos to @dandavison 🙏.
⚠️ The Workflow Update feature is currently in pre-release stage. Related APIs are considered experimental and may change in the near future. This feature requires Temporal Server version 1.21 or later, and must be enabled through configuration.Here are some examples to get started:
// An Update is defined similarly to Query and Signal. The second type parameter (input) is optional const myUpdate = wf.defineUpdate<string, [string]>('myUpdate'); // A handler is a sync or async function; the optional validator is always sync. wf.setHandler(myUpdate, handler, { validator }); // `await wfHandle.executeUpdate` to start an update and block until it completes or fails. // The signature resembles `WorkflowClient.start` in that the arguments are supplied // in the optional options object // (as opposed to the variadic signatures of wfHandle.query and wfHandle.signal) // An error is thrown if the RPC times out (we do not poll for this call) // or if the validator rejects, or the handler throws ApplicationFailure. const updateResult = await handle.executeUpdate(myUpdate, { args: ['a'] }); // `startUpdate` to block until Accepted and obtain a handle to the Update. Signature the same as `executeUpdate`. const updateHandle = await handle.startUpdate(myUpdate, { args: ['a'] }); // Blocking call to fetch result, with polling. // Validation failures or ApplicationFailure in the handler would throw here. const updateResult = await updateHandle.result();
Major overhaul of logging support
This release features a major overhaul of Workflow logging, Activity logging support, and Core log forwarding support.
These changes add up to better DX, notably by promoting clear usage patterns that work out of the box, removing the need to use insufficiently documented features, and removing some requirements that were error-prone and a recurring source of support questions.
Workflow Logging
-
Calling
appendDefaultInterceptors()
when registering custom interceptors is no longer required in order for Workflow logger support to work properly.appendDefaultInterceptors()
is now deprecated and behave as a no-op in most cases (i.e. except when a custom logger is supplied in order to redirect log emitted by activities) (#1290). -
Similarly, calling
defaultSink()
when registering custom sinks is no longer required for Workflow logging to work properly (#1283). SDK's logger sink is now forcibly injected. Registering a custom logger sink nameddefaultWorkerLogger
is still supported, but discouraged and deprecated. -
Attributes from the current Workflow are now automatically included as metadata on every log entries emitted using the Workflow context logger, and some key events of the Workflow's lifecycle are now automatically logged (at
DEBUG
level for most messages;WARN
for failures) (#1290). -
WorkflowLogInterceptor
has been deprecated (#1290).
Activity Logging
-
Attributes from the current Activity context are now automatically included as metadata on every log entries emitted using the Activity context logger, and some key events of the Activity's lifecycle are now automatically logged (at
DEBUG
level for most messages;WARN
for failures) (#1284). -
Logged Activity attributes can be customized by registering an
ActivityOutboundCallsInterceptor
that intercepts thegetLogAttributes()
method (#1284). -
ActivityInboundLogInterceptor
has been deprecated; registering such an interceptor with a custom logger still works, but will prevent the use of the newly introducedgetLogAttributes()
interceptor. The same is true about modifying the context logger directly (e.g.context.log = myCustomLogger
) (#1284).
Core Logging
-
Log entries forwarded from Core to TS now retain metadata (#1225).
-
Reduce the default Core log level to
WARN
and non Core Rust logs toERROR
(#1270).
Improvements to Activity-related APIs
-
💥 BREAKING: Non-local activities may now receive cancellation for schedule-to-close and start-to-close timeouts, without heartbeating. Heartbeating is still required to receive server sent cancellations.
-
All APIs provided through
Context.current()
can now be accessed directly from the@temporalio/activity
import (#1252). Also, thanks to @SergeiMeza for fixing a related issue that would occur whenheartbeat
andsleep
functions were destructured out ofContext.current()
(#1249) 🙏.Instead of this:
import { Context } from `@temporalio/activity`; // ... Context.current().sleep('5 minutes');
you can now do:
import { sleep } from `@temporalio/activity`; // ... sleep('5 minutes');
-
ActivityInboundCallsInterceptorFactory
has been deprecated. Activity Interceptors should now be instantiated using anActivityInterceptorsFactory
(#1284). -
MockActivityEnvironment
now accept a list of activity interceptors, and more closely match an actual activity execution (#1284).
Improvements to Client-related APIs
-
Allow specifying gRPC
CallCredentials
on Client connection. Also, fixed the fact thatConnectionOptions.credentials
was completely ignored (#1261). -
Client
andConnection
classes now expose awithAbortSignal()
method. This can be used to cancel all ongoing connections started within a function's scope (#1272).Example:
const ctrl = new AbortController(); setTimeout(() => ctrl.abort(), 10_000); // 👇 throws if incomplete by the timeout. await conn.withAbortSignal(ctrl.signal, () => { /* make client or direct grpc calls */);
-
Add
startDelay
option to workflowstart
,execute
, andsignalWithStart
(#1300).
Miscellaneous
-
WorkerOptions
now exposes anonStickyToStickyPollRatio
property, which controls the proportion of Workflow Task Pollers assigned to polling from Regular Task Queue vs to polling from Sticky Tasks Queues (#1254). -
Fixed a bug that could happen if the
@temporalio/proto
was minimized (#1279). -
Prevents a Workflow-only Worker from requesting Eager Activity Execution for activities schedules on the same task queue name (#1326).
1.9.0-rc0
Support for Workflow Update (experimental)
-
Introduced Workflow Update APIs, both on Workflow side and Client side (#1277 and #1312). Kudos to @dandavison 🙏.
⚠️ The Workflow Update feature is currently in Public Preview. Related APIs are considered experimental and may change in the near future. This feature requires Temporal Server version 1.21 or later, and must be enabled through configuration.Here are some examples to get started:
// An Update is defined similarly to Query and Signal. The second type parameter (input) is optional const myUpdate = wf.defineUpdate<string, [string]>('myUpdate'); // A handler is a sync or async function; the optional validator is always sync. wf.setHandler(myUpdate, handler, { validator }); // `await wfHandle.executeUpdate` to start an update and block until it completes or fails. // The signature resembles `WorkflowClient.start` in that the arguments are supplied // in the optional options object // (as opposed to the variadic signatures of wfHandle.query and wfHandle.signal) // An error is thrown if the RPC times out (we do not poll for this call) // or if the validator rejects, or the handler throws ApplicationFailure. const updateResult = await handle.executeUpdate(myUpdate, { args: ['a'] }); // `startUpdate` to block until Accepted and obtain a handle to the Update. // Signature the same as `executeUpdate`. const updateHandle = await handle.startUpdate(myUpdate, { args: ['a'] }); // Blocking call to fetch result, with polling. // Validation failures or ApplicationFailure in the handler would throw here. const updateResult = await updateHandle.result();
Major overhaul of logging support
This release feature a major overhaul of Workflow logging, Activity logging support, and Core log forwarding support.
These changes add up to better DX, notably by promoting clear usage patterns that work out of the box, remove the need to use insufficiently documented features, and remove some requirements that were error-prone and recurring source of support questions.
Workflow Logging
-
Calling
appendDefaultInterceptors()
when registering custom interceptors is no longer required in order for Workflow logger support to work properly.appendDefaultInterceptors()
is now deprecated and behave as a no-op in most cases (i.e. except when a custom logger is supplied in order to redirect log emitted by activities) (#1290). -
Similarly, calling
defaultSink()
when registering custom sinks is no longer required for Workflow logging to work properly (#1283). SDK's logger sink is now forcibly injected. Registering a custom logger sink nameddefaultWorkerLogger
is still supported, but discouraged and deprecated. -
Attributes from the current Workflow are now automatically included as metadata on every log entries emited using the Workflow context logger, and some key events of the Workflow's lifecycle are now automatically logged (at
DEBUG
level for most messages;WARN
for failures) (#1290). -
WorkflowLogInterceptor
has been deprecated (#1290).
Activity Logging
-
Attributes from the current Activity context are now automatically included as metadata on every log entries emited using the Activity context logger, and some key events of the Activity's lifecycle are now automatically logged (at
DEBUG
level for most messages;WARN
for failures) (#1284). -
Logged Activity attributes can be customized by registering an
ActivityOutboundCallsInterceptor
that intercepts thegetLogAttributes()
method (#1284). -
ActivityInboundLogInterceptor
has been deprecated; registering such an interceptor with a custom logger still works, but will prevent the use of the newly introducedgetLogAttributes()
interceptor. The same is true about modifying the context logger directly (eg.context.log = myCustomLogger
) (#1284).
Core Logging
-
Log entries forwarded from Core to TS now retain metadata (#1225).
-
Reduce the default Core log level to
WARN
and non Core Rust logs toERROR
(#1270).
Improvements to Activity-related APIs
-
💥 Remote activities may now receive cancellation for schedule-to-close and start-to-close timeouts, without heartbeating. Heartbeating is still required to receive server sent cancellations.
-
All APIs provided through
Context.current()
can now be accessed directly from the@temporalio/activity
import (#1252). Also, thanks to @SergeiMeza for fixing a related issue that would occur whenheartbeat
andsleep
functions were destructured out ofContext.current()
(#1249) 🙏.Instead of this:
import { Context } from `@temporalio/activity`; // ... Context.current().sleep('5 minutes');
you can now do:
import { sleep } from `@temporalio/activity`; // ... sleep('5 minutes');
-
ActivityInboundCallsInterceptorFactory
has been deprecated. Activity Interceptors should now be instantiated using anActivityInterceptorsFactory
(#1284). -
MockActivityEnvironment
now accept a list of activity interceptors, and more closely match an actual activity execution (#1284).
Improvements to Client-related APIs
-
Allow specifying gRPC
CallCredentials
on Client connection. Also, fixed the fact thatConnectionOptions.credentials
was completely ignored (#1261). -
Client
andConnection
classes now expose awithAbortSignal()
method. This can be used to cancel all ongoing connections started within a function's scope. (#1272)Example:
const ctrl = new AbortController(); setTimeout(() => ctrl.abort(), 10_000); // 👇 throws if incomplete by the timeout. await conn.withAbortSignal(ctrl.signal, () => { /* make client or direct grpc calls */);
-
Add
startDelay
to workflow (signal) start options (#1300)
Miscellaneous
-
WorkerOptions
now expose anonStickyToStickyPollRatio
property, which control the proportion of Workflow Task Pollers assigned to polling from Regular Task Queue vs to polling from Sticky Tasks Queues. (#1254) -
Enable
reuseV8Context
by default (#1310) -
Fixed a bug that could happen if the
@temporalio/proto
was minimized (#1279)
v1.8.6
v1.8.5
Features
-
[
workflow
] AddhistorySizeInBytes
andcontinueAsNewSuggested
toWorkflowInfo
(#1223). -
Remove experimental flags on the following APIs (#1235):
- Schedules
- Failure Converters
- Local Activities
- Replay Histories
Bug Fixes
-
[
workflow
] Fix a particular case whereisReplaying
could be incorrectlyfalse
if a query came in right after a cache eviction and there had been a signal in the last workflow task before the cache eviction (#1234). This could notably cause situations where calls to sink functions configured withcalledDuringReplay = false
would be called more than once for a same code location, as well as situations wherepatched(...)
would return true even though that patch had not been set on the first execution. -
[
workflow
] MakeExternalWorkflowHandle.signal
signature match that ofBaseWorkflowHandle.signal
(#1237). Thanks to@gabrielsantosblanchet
and@josiah-roberts
🙏. -
[
worker
] Avoid rxjs error while flushing logs on worker shutdown (#1238). -
Upgrade
protobufjs
dependencies (#1219). Thanks to@alex-dixon
🙏.
1.8.4
Bug Fixes
-
[
workflow
] Fix a particular case whereisReplaying
would be incorrectlytrue
if a query came in right after a cache eviction (#1211). This could notably cause situations where calls to sink functions not configured withcalledDuringReplay = true
would not be relayed, as well as situations wherepatched(...)
would return false even though this part of the code was being executed for the first time. -
[
workflow
] Avoid errors in Search Attributes Payload Converter if enumerable properties had been added onArray.prototype
(#1209). -
[
worker
] ValidatebuildId
is set whenuseVersioning
is true (#1207).
1.8.3
Bug Fixes
- [
workflow
] Remove accidental import of large protos into workflow library (#1203). This had been causing significant memory usage increases on workflow workers since 1.8.1. - [
core
] Fix removal of deprecated patch call adjacent to other patch (#587) - [
client
] ThrowWorkflowExecutionAlreadyStartedError
onsignalWithStart
(#1199 thanks to@satazor
🙏)
1.8.2
Features
-
[
workflow
] Add support for URL/URLSearchParams inside the workflow sandbox (#1173) -
[
worker
] Export theWorkerStatus
interface (#1184).
Bug Fixes
-
Use custom symbol-based implementation of
instanceof
for all of our custom error classes (#1166). This is a complete rework of the fixes introduced in 1.8.0 and 1.8.1, both of which turned out to be insufficient.instanceof
now works correctly both across execution contexts and when running tests with Jest.💥 The static
is
function introduced previously on some of our error classes is no longer required, as theinstanceof
operator itself now behave correctly; theseis
functions have therefore been removed. -
[
client
] Makeaction.workflowId
optional on Schedule update (#1176) -
[
activity
]heartbeatTimeoutMs
is now correctly set on Activity'sContext.current().info
. ThecurrentAttemptScheduledTimestampMs
property has also been added to that data structure (#1187) -
[
workflow
] The Workflow Bundler is now smarter regarding requiring files with incorrect or missing file extensions (#1186). Thanks to@GauBen
🙏. -
[
workflow
] Fix incorrect values ofworkflowInfo.historyLength
andworkflowInfo.unsafe.isReplaying
as reported in out-of-sandbox log messages, and as argument of sink function implementations (#1181). -
[
workflow
] Sink functions configured withcallDuringReplay = false
are no longer invoked from a replay-only worker (i.e.Worker.runReplayHistories()
); it was previously possible for these to get called in some cases on the very last Workflow Task (#1181). -
[
workflow
] Encoding and decoding of protobuf JSON payloads is now working in workflow context. This was previously failing, due to the fact that an underlying library depends on theBuffer
class, which is not available inside the workflow sandbox (#1170). Thanks to@antlai-temporal
🙏. -
[
core
] Improve warning message on error response to completion (temporalio/sdk-core#581) Thanks to@dandavison
🙏. -
[
core
] Fix abandoned children nondeterminism if they complete (temporalio/sdk-core#580).
1.8.1
Bug Fixes
-
Remove
instanceof Error
checks fromis
methods (#1162). This fixes various regressions observed when running Workflow tests with Jest. -
Add the
VersioningIntent
parameters on the experimental worker versioning feature (#1156). Also, all values of theReachabilityType
type has been converted to SCREAMING_CASE. -
[
workflow
] Makeworkflows.log()
'sattributes
argument optional (#1159). -
[
workflow
] Attachworkflows.log()
'sattributes
to every workflow log message (#1159).
1.8.0
Features
-
[
worker
] Add support for worker versioning (#1146).Worker versioning is available from server version 1.21 (if enabled in dynamic configuration).
⚠️ Experimental - While the feature is well tested and is considered functionally stable, the SDK APIs are considered experimental.To use worker versioning with the TypeScript SDK, use the following steps:
import { Client } from '@temporalio/client'; import { Worker } from '@temporalio/worker'; const buildId = 'id-generated-from-continuous-integration'; // Deploy new workers, opt them in to versioning. const worker = await Worker.create({ workflowsPath: require.resolve('./workflows'), buildId, useVersioning: true, // ... }); // ... // In a separate process, when all workers are up, update the build id compatibility for the task queue. const client = new Client({ /* options */ }); // If the current version is incompatible with the previous ones: await client.taskQueue.updateBuildIdCompatibility('my-task-queue', { operation: 'addNewIdInNewDefaultSet', buildId, }); // Or, if the current version is compatible with a previous one: await client.taskQueue.updateBuildIdCompatibility('my-task-queue', { operation: 'addNewCompatibleVersion', buildId, existingCompatibleBuildId: 'some-other-build-id', }); // Check if workers are reachable before retiring them (even if their build ids are associated with multiple task // queues): const { buildIdReachability } = await client.taskQueue.getReachability({ buildIds: ['some-other-build-id'] }); const { taskQueueReachability } = buildIdReachability['some-other-build-id']; for (const [taskQueue, reachability] of Object.entries(taskQueueReachability)) { if (reachability.length > 0) { if (reachability[0] === 'NotFetched') { // We asked the server for too many reachability entries (build ids or task queues), // This build id / task queue reachability should be fetched in another request. // Fetch this information here or later... } else { console.log('Build id still reachable on:', taskQueue); } } } // Check if build id is reachable...
-
[
worker
] Add support for using multiple concurrent pollers to fetch Workflow Tasks and Activity Tasks from Task Queues (#1132).The number of pollers for each type can be controlled through the
WorkerOptions.maxConcurrentWorkflowTaskPolls
andWorkerOptions.maxConcurrentActivityTaskPolls
properties. Properly adjusting these values should allow better filling of the corresponding execution slots, which may signficiantly improve a Worker's throughput. Defaults are 10 Workflow Task Pollers and 2 Activity Task Pollers; we however strongly recommend tuning these values based on workload specific performance tests.Default value for
maxConcurrentWorkflowTaskExecutions
has also been reduced to 40 (it was previously 100), as recent performance tests demonstrate that higher values increase the risk of Workflow Task Timeouts unless other options are also tuned. This was not problem previously because the single poller was unlikely to fill all execution slots, so maximum would rarely be reached. -
[
workflow
] ThereuseV8Context
worker option is no longer marked as experimental (#1132). This is a major optimization of the Workflow sandboxing runtime; it allows the worker to reuse a single execution context across Workflow instances, without compromising the safety of the deterministic sandbox. It significantly reduces RAM and CPU usage. The formula used to auto-configuremaxCachedWorkflows
has also been reviewed to reflect a lower memory usage requirement whenreuseV8Context
is enabled.At this point, you still need to opt-in to this feature by adding
reuseV8Context: true
to yourWorkerOptions
, as we believe most teams should reconsider their workers's performance settings after enabling this option.💥 Note that we are planing enabling this option by default starting with 1.9.0. If for some reason, you prefer to delay enabling this optimization, then we recommend that you explicitly add
reuseV8Context: false
to your worker options. -
We now provide out-of-the-box log support from both Workflows and Activity contexts (#1117, #1138)).
For Workflows, the logger funnels messages through the
defaultWorkerLogger
sink, which itself defaults to forwarding messages toRuntime.instance().logger
.Example usage:
import * as workflow from '@temporalio/workflow'; export async function myWorkflow(): Promise<void> { workflow.log.info('hello from my workflow', { key: 'value' }); }
For Activities, the logger can be accessed as
Context.log
. It defaults toRuntime.instance().logger
, but may be overridden by interceptors (i.e. to set a custom logger).ActivityInboundLogInterceptor
is still installed by default, adding enriched metadata from activity context on each log entry.Example usage:
import * as activity from '@temporalio/activity'; export async function myActivity(): Promise<void> { const context = activity.Context.current(); context.log.info('hello from my activity', { key: 'value' }); }
-
💥 Protect against 'ms' durations errors (#1136). There have been several reports of situations where invalid durations resulted in unexpected and hard to diagnose issues (e.g. can you predict what
const bool = condition(fn, '1 month')
will do?). We now provide type definitions for "ms-formatted strings" through the newly introducedDuration
type, which is either a well-formedms
-formatted string or a number of milliseconds. Invalid ms-formatted-strings will also throw at runtime.Note: this might cause build errors in situations where a non-const string value is passed somewhere we expect a
Duration
. Consider either validating and converting these strings before passing them asDuration
, or simply cast them toDuration
and deal with runtime exceptions that might be thrown if an invalid value is provided. -
[
workflow
] Clone sink args at call time on Node 17+ (#1118). A subtle aspect of Workflow Sinks is that calls are actually buffered and get executed only once the current Workflow activation completes. That sometime caused unexpected behavior where an object passed as argument to a sink function is mutated after the invocation.On Node.js 17+, we now clone sink arguments at call time, using
structuredClone
. While this adds some runtime overhead, it leads to more predictable experience, as well as better exceptions when passing non-transferrable objects to a sink. -
[
core
] Add thesticky_cache_total_forced_eviction
metric (Core #569) -
[
client
] Throw more specific errors from Client APIs (#1147)
Bug Fixes
- [
core
] Metrics that should be produced by the SDK Core's internal Client would previously not get emitted. This has been fixed. (#1119) - [
client
] Fix incorrect schedule spec boundaries checks on hour and day of month (#1120) - [
workflow
] We now throw more meaningful errors when Workflow-only APIs are used from non-Workflow context, and some other situations. (#1126) - Removed most
instanceof
checks from SDK, and replaced them byXxxError.is(...)
checks, based on the presence of a symbol property. We believe this should help resolve most of the problems that previously been observed when multiple copies or different versions of SDK packages are installed in a same project ((#1128)). - [
workflow
] Make Local Activity timeouts inActivityInfo
match those of non-Local Activities (#1133, Core #569). - [
workflow
] Ensure payload converters keep Uint8Array type equality (#1143) - Fail workflow task if local activity not registered with worker (#1152)
- [
core
] Don't increment terminal command metrics when replaying (Core #572) - [
core
] Fix start-to-close local activity timeouts not being retriable like they should be (#Core 576)
Documentation
- Improve documentation for activity heartbeat and cancellationSignal (#1151)