Skip to content

Commit 97d8073

Browse files
authored
Merge branch 'main' into mm/worker-versioning-steps
2 parents 54f6c0b + 6bebbc2 commit 97d8073

8 files changed

Lines changed: 113 additions & 24 deletions

File tree

docs/cloud/get-started/namespaces.mdx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ Each Namespace Name must conform to the following rules:
4747
- A Namespace Name must contain at least 2 characters and no more than 39 characters.
4848
- A Namespace Name must begin with a letter, end with a letter or number, and contain only letters, numbers, and the
4949
hyphen (-) character.
50-
- Each hyphen (-) character must be immediately preceded _and_ followed by a letter or number; consecutive hyphens are
51-
not permitted.
5250
- All letters in a Namespace Name must be lowercase.
5351

5452
## What is a Temporal Cloud Account ID? {#temporal-cloud-account-id}

docs/develop/go/best-practices/data-handling/external-storage.mdx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ c, err := client.Dial(client.Options{
254254
})
255255
```
256256

257+
You can also package your driver as a [plugin](/develop/plugins-guide) for easier reuse across services.
258+
257259
## Configure payload size threshold
258260

259261
You can configure the payload size threshold that triggers external storage. By default, payloads larger than 256 KiB
@@ -278,8 +280,7 @@ c, err := client.Dial(client.Options{
278280
## Use multiple storage drivers
279281

280282
When you register multiple drivers, you must provide a `DriverSelector` that implements the `StorageDriverSelector`
281-
interface. The SDK returns an error at client creation if multiple drivers are registered without a selector. The
282-
selector chooses which driver stores each payload. Any driver in the list that is not selected for storing is still
283+
interface. The selector chooses which driver stores each payload. Any driver in the list that is not selected for storing is still
283284
available for retrieval, which is useful when migrating between storage backends. Return `nil` from the selector to
284285
keep a specific payload inline in Event History.
285286

@@ -288,9 +289,9 @@ Multiple drivers are useful in scenarios such as:
288289
- Driver migration. Your Worker needs to retrieve payloads created by clients that use a different driver than the
289290
one you prefer. Register both drivers and use the selector to always pick your preferred driver for new payloads.
290291
The old driver remains available for retrieving existing claims.
291-
- Latency vs. durability tradeoffs. Some Workflow types may benefit from a faster storage backend
292-
at the cost of durability, while others require a durable backend like S3. Use the selector to route based on
293-
Workflow context.
292+
- Multi-cloud storage. Route payloads to different storage backends based on your cloud environment. For
293+
example, use S3 for Workers running on AWS and GCS for Workers running on Google Cloud. The selector chooses the
294+
appropriate driver based on the runtime environment.
294295

295296
The following example registers two drivers but always selects `preferredDriver` for new payloads. The `legacyDriver`
296297
is only registered so the Worker can retrieve payloads that were previously stored with it:

docs/develop/python/best-practices/data-handling/external-storage.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ Multiple drivers are useful in scenarios such as:
220220
- Driver migration. Your Worker needs to retrieve payloads created by clients that use a different driver than the
221221
one you prefer. Register both drivers and use the selector to always pick your preferred driver for new payloads.
222222
The old driver remains available for retrieving existing claims.
223-
- Latency vs. durability tradeoffs. Some Workflow types may benefit from a faster storage backend
224-
at the cost of durability, while others require a durable backend like S3. Use the selector to route based on
225-
Workflow context.
223+
- Multi-cloud storage. Route payloads to different storage backends based on your cloud environment. For
224+
example, use S3 for Workers running on AWS and GCS for Workers running on Google Cloud. The selector chooses the
225+
appropriate driver based on the runtime environment.
226226

227227
The following example registers two drivers but always selects `preferred_driver` for new payloads. The `legacy_driver`
228228
is only registered so the Worker can retrieve payloads that were previously stored with it:

docs/develop/typescript/nexus/feature-guide.mdx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,14 @@ export const helloServiceHandler = nexus.serviceHandler(helloService, {
190190
A common pattern is to use the Temporal Client from within a sync handler to Signal, Query, or Update a Workflow.
191191
You can also use Signal-With-Start or Update-With-Start to ensure the Workflow is started and send it a Signal or Update.
192192
All calls must complete within the [Nexus request timeout](/cloud/limits#nexus-operation-request-timeout).
193-
The handler receives an AbortSignal via options.signal that is triggered when the deadline is exceeded
193+
The handler receives an AbortSignal via `ctx.abortSignal` that is triggered when the deadline is exceeded
194194
pass it to Temporal Client calls to ensure they are canceled if the timeout is reached.
195195
Updates should be short-lived to stay within this deadline.
196196

197+
The handler context also exposes `ctx.requestDeadline` as an optional `Date`, representing the time by which the current request must complete.
198+
Note that this is the deadline for the current _request_, not the overall operation.
199+
Use it to make decisions about whether to start work that may not finish in time, or to set timeouts on downstream calls.
200+
197201
### Develop an Asynchronous Nexus Operation handler to start a Workflow
198202

199203
Use `@temporalio/nexus`'s `WorkflowRunOperationHandler` helper class to easily expose a Temporal Workflow as a Nexus Operation.
@@ -269,7 +273,7 @@ import { helloServiceHandler } from './handler';
269273

270274
## Develop a caller Workflow that uses the Nexus Service {#develop-caller-workflow-nexus-service}
271275

272-
To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use `@temporalio/workflow`'s `createNexusClient` to create a Nexus client for that service.
276+
To execute a Nexus Operation from a Workflow, import the necessary service definition types, then use `@temporalio/workflow`'s `createNexusServiceClient` to create a Nexus client for that service.
273277
You will need to provide the Nexus Endpoint name, which you registered previously in [Create a Nexus Endpoint to route requests from caller to handler](#create-nexus-endpoint).
274278

275279
<!--SNIPSTART typescript-nexus-hello-service-caller-workflow {"selectedLines": ["1-5","21-34"]}-->
@@ -283,7 +287,7 @@ import { helloService, LanguageCode } from "../service/api";
283287
const HELLO_SERVICE_ENDPOINT = "hello-service-endpoint-name";
284288

285289
export async function helloCallerWorkflow(name: string, language: LanguageCode): Promise<string> {
286-
const nexusClient = wf.createNexusClient({
290+
const nexusClient = wf.createNexusServiceClient({
287291
service: helloService,
288292
endpoint: HELLO_SERVICE_ENDPOINT,
289293
});
@@ -440,6 +444,36 @@ For **synchronous Nexus Operations** the following are reported in the caller's
440444

441445
:::
442446

447+
### OpenTelemetry
448+
449+
The `@temporalio/interceptors-opentelemetry` package supports Nexus Operations, providing automatic trace context propagation across Nexus boundaries from the caller Workflow to the handler.
450+
451+
The easiest way to enable it is with the `OpenTelemetryPlugin`, which auto-registers Nexus interceptors alongside Activity and Workflow interceptors:
452+
453+
```ts
454+
import { OpenTelemetryPlugin } from '@temporalio/interceptors-opentelemetry';
455+
456+
const plugin = new OpenTelemetryPlugin({
457+
resource: myResource,
458+
spanProcessor: mySpanProcessor,
459+
});
460+
461+
const worker = await Worker.create({
462+
// ...
463+
plugins: [plugin],
464+
nexusServices: [myServiceHandler],
465+
});
466+
```
467+
468+
The plugin creates the following spans:
469+
470+
- **Caller side:** `StartNexusOperation:service/operation` — created when the caller Workflow starts a Nexus Operation.
471+
- **Handler side:** `RunStartNexusOperation:service/operation` and `RunCancelNexusOperation:service/operation` — created when the handler processes the operation. These spans are children of the caller span, linked via trace context propagated in Nexus request headers.
472+
473+
See the [interceptors-opentelemetry sample](https://github.com/temporalio/samples-typescript/tree/main/interceptors-opentelemetry) for a complete example.
474+
475+
For custom interceptor logic beyond tracing (e.g., logging, authorization), see [Nexus interceptor registration](/develop/typescript/workers/interceptors#nexus-interceptor-registration).
476+
443477
## Learn more
444478

445479
- Read the high-level description of the [Temporal Nexus feature](/evaluate/nexus) and watch the [Nexus keynote and demo](https://youtu.be/qqc2vsv1mrU?feature=shared&t=2082).

docs/develop/typescript/nexus/quickstart.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ The `nexusServices` parameter registers the handler so it can receive Nexus Oper
166166
<>
167167

168168
<CodeSnippet language="typescript">
169-
{`import { proxyActivities, createNexusClient } from '@temporalio/workflow';
169+
{`import { proxyActivities, createNexusServiceClient } from '@temporalio/workflow';
170170
// Only import the activity types
171171
import type * as activities from './activities';
172172
import { sayHelloService } from './service';
@@ -183,7 +183,7 @@ export async function example(name: string): Promise<string> {
183183
const NEXUS_ENDPOINT = 'my-nexus-endpoint-name';
184184
185185
export async function callerWorkflow(name: string): Promise<string> {
186-
const nexusClient = createNexusClient({
186+
const nexusClient = createNexusServiceClient({
187187
service: sayHelloService,
188188
endpoint: NEXUS_ENDPOINT,
189189
});
@@ -203,7 +203,7 @@ The caller Workflow demonstrates the consumer side of Nexus.
203203
Instead of importing handler code directly, the caller only depends on the Service contract.
204204
This keeps the caller and handler decoupled so they can live in separate Namespaces, repositories, or even teams.
205205

206-
The `wf.createNexusClient()` method creates a client bound to your Nexus Service and Endpoint.
206+
The `wf.createNexusServiceClient()` method creates a client bound to your Nexus Service and Endpoint.
207207
`executeOperation` starts the operation and waits for the result.
208208

209209
</SetupStep>

docs/develop/typescript/workers/interceptors.mdx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ sample.
4444
Intercept workflow-related methods of [`Client`](https://typescript.temporal.io/api/classes/client.Client/) and
4545
[`WorkflowHandle`](https://typescript.temporal.io/api/interfaces/client.WorkflowHandle) like starting or signaling a
4646
Workflow.
47+
- [NexusInboundCallsInterceptor](https://typescript.temporal.io/api/interfaces/worker.NexusInboundCallsInterceptor):
48+
Intercept inbound Nexus Operation calls like `startOperation` and `cancelOperation`.
49+
- [NexusOutboundCallsInterceptor](https://typescript.temporal.io/api/interfaces/worker.NexusOutboundCallsInterceptor):
50+
Intercept outbound calls from Nexus Operations, such as enriching log attributes and metric tags.
4751

4852
All interceptor methods are optional—it's up to the implementor to choose which methods to intercept.
4953

@@ -74,6 +78,36 @@ export class ActivityLogInterceptor implements WorkflowOutboundCallsInterceptor
7478
}
7579
```
7680

81+
**Log Nexus Operations**
82+
83+
```ts
84+
import type {
85+
NexusInboundCallsInterceptor,
86+
NexusStartOperationInput,
87+
NexusStartOperationOutput,
88+
Next,
89+
} from '@temporalio/worker';
90+
91+
export class NexusOperationLogInterceptor implements NexusInboundCallsInterceptor {
92+
async startOperation(
93+
input: NexusStartOperationInput,
94+
next: Next<NexusInboundCallsInterceptor, 'startOperation'>
95+
): Promise<NexusStartOperationOutput> {
96+
console.log('Starting Nexus operation', {
97+
service: input.ctx.service,
98+
operation: input.ctx.operation,
99+
});
100+
const output = await next(input);
101+
console.log('Nexus operation started', {
102+
service: input.ctx.service,
103+
operation: input.ctx.operation,
104+
async: output.result.isAsync,
105+
});
106+
return output;
107+
}
108+
}
109+
```
110+
77111
## Register an Interceptor {#register-interceptor}
78112

79113
Registering an interceptor means providing it to the SDK so Temporal can invoke it when matching Client or Worker calls occur. Once registered, it runs in the call path and can observe or modify request and response data.
@@ -125,3 +159,29 @@ const worker = await Worker.create({
125159
},
126160
});
127161
```
162+
163+
### Nexus interceptor registration
164+
165+
Nexus interceptors are registered on Worker creation via
166+
[WorkerOptions](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#interceptors).
167+
Pass an array of factory functions to `interceptors.nexus`.
168+
Each factory receives an [`OperationContext`](https://typescript.temporal.io/api/classes/nexus.OperationContext) and returns an object with optional `inbound` and `outbound` interceptors.
169+
170+
`src/worker/index.ts`
171+
172+
```ts
173+
import { NexusOperationLogInterceptor } from './nexus-interceptors';
174+
175+
const worker = await Worker.create({
176+
// ...
177+
nexusServices: [/* your Nexus services */],
178+
interceptors: {
179+
nexus: [
180+
(_ctx) => ({
181+
inbound: new NexusOperationLogInterceptor(),
182+
}),
183+
],
184+
},
185+
});
186+
```
187+

docs/encyclopedia/activities/activity-operations.mdx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ temporal activity pause \
7777
--reason "Downstream API is down, pausing until recovery"
7878
```
7979

80-
See the [CLI reference for `temporal activity pause`](/cli/activity#pause) for all options, including `--activity-type` for targeting by type.
80+
See the [CLI reference for `temporal activity pause`](/cli/activity#pause) for all options.
8181

8282
### Detect Pause in Activity code
8383

@@ -104,11 +104,9 @@ Your Activity code may need to handle these cases differently, for example relea
104104

105105
- **A Paused Activity can still time out.** Pause doesn't stop or extend the [Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout). Use [`update-options`](#update-options) to adjust the timeout if needed.
106106
- **Pause won't interrupt an Activity that doesn't Heartbeat.** The current execution runs to completion, which could take up to the full [Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout).
107-
- **Pausing by `--activity-type` doesn't prevent new Activities of that type from running.** The command Pauses Activities that are pending when it runs. Activities that start afterward are unaffected.
108-
109107
### Limitations
110108

111-
- **Pause doesn't support bulk operations.** Unlike Unpause, Reset, and Update Options, there's no `--query` flag. You can only Pause Activities within a single Workflow, by Activity Id or by Activity type.
109+
- **Pause operates on individual Activities by ID within a single Workflow.** Unlike Unpause, Reset, and Update Options, there's no `--query` flag. To pause multiple Activities, issue separate commands for each Activity ID.
112110
- **No Namespace-wide query for Paused Activities.** You must know the Workflow Id. See [Observability](#observability).
113111

114112
## Unpause {#unpause}
@@ -206,7 +204,6 @@ Your Activity code may need to handle these cases differently, for example savin
206204
- **Heartbeat details are preserved by default.** If your Activity uses Heartbeat details for progress tracking and you want a clean restart, pass `--reset-heartbeats`.
207205
- **Reset won't interrupt an Activity that doesn't Heartbeat.** The current execution runs to completion, which could take up to the full [Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout). If the Activity had already retried (attempt > 1), the Temporal Service rejects the current execution's result because Reset changed the expected attempt number. The Activity waits for its Start-To-Close Timeout to expire before a new execution is scheduled.
208206
- **`--restore-original-options` restores the Activity's original configuration.** It reverts timeouts, Retry Policy, and Task Queue to the values from when the Activity was first scheduled.
209-
- **Resetting by `--activity-type` or `--match-all` doesn't affect new Activities of that type.** The command Resets Activities that are pending when it runs. Activities that start afterward are unaffected.
210207
- **Bulk Reset can overwhelm downstream services.** When using `--query` to Reset Activities across many Workflows, use `--jitter` to stagger the restart times.
211208

212209
## Update Options {#update-options}
@@ -247,7 +244,6 @@ See the [CLI reference for `temporal activity update-options`](/cli/activity#upd
247244
### Important considerations
248245

249246
- **Changes to a running Activity take effect on the next execution, not the current one.** If you need the change to apply immediately, the Activity must finish or fail its current execution first.
250-
- **Updating by `--activity-type` or `--match-all` doesn't affect new Activities of that type.** The command updates Activities that are pending when it runs. Activities that start afterward are unaffected.
251247
- **`--restore-original-options` is batch-only.** This flag only works with `--query`. It's silently ignored in single-workflow mode. It can't be combined with other option changes in the same command.
252248

253249
### Limitations
@@ -265,7 +261,7 @@ The UI shows who performed an operation, when, and why (if a `--reason` was prov
265261

266262
### Find Paused Activities
267263

268-
The `TemporalPauseInfo` [Search Attribute](/search-attribute) is filterable by Activity type within a Workflow.
264+
The `TemporalPauseInfo` [Search Attribute](/search-attribute) is filterable within a Workflow.
269265

270266
There's no Namespace-wide query to find all Paused Activities across Workflows.
271267
You must know the Workflow Id.

docs/with-ai.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Install the Temporal plugin from the [Cursor Marketplace](https://cursor.com/mar
4444
command in Cursor's agent chat:
4545

4646
```
47-
/add-plugin temporal-developer
47+
/add-plugin temporal
4848
```
4949

5050
</TabItem>

0 commit comments

Comments
 (0)