Skip to content

feat(nexus): Initial Nexus support #1708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open

Conversation

bergundy
Copy link
Member

@bergundy bergundy commented May 8, 2025

What changed

This adds support for Nexus in the Temporal TS SDK.

All Nexus-related APIs are experimental, and some changes are expected before this reaches GA.

Review notes

  • The team is still discussing expectations regarding HandlerError and OperationError serialization. In the mean time, this PR mostly adheres to the original spec (i.e. a Nexus should have either a message or a cause), but little effort has been put into compliance testing, as we know the present model is broken in certain aspects.
  • Tests use the HTTP API directly in lieu of a workflow caller or strongly typed client, we can refactor those later.
  • More tests will continue to be added in the near future, after this PR.

@bergundy bergundy requested a review from a team as a code owner May 8, 2025 01:26
test.beforeEach(async (t) => {
const taskQueue = t.title + randomUUID();
const { env } = t.context;
const response = await env.connection.operatorService.createNexusEndpoint({
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand this is the way for now, but what's the intent regarding this? Do we expect users to have to do the same in their own Temporal+Nexus tests?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good question. in Java we have a shortcut, we should probably make it easier in the test environment for Core based SDKs. @dandavison, wondering if you considered this in Python.

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I have not considered APIs for user Temporal Nexus testing yet; I'll add a TODO to the code. In my tests I have a helper function like this for creating Nexus endpoints.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we need an issue to track this.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Created one for Python SDK-3837 "Python Nexus user testing utilities and docs"

Copy link
Member Author

Choose a reason for hiding this comment

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

I would have made this a features issue with checkboxes for each SDK but 🤷, thanks!

options: nexus.StartOperationOptions
): Promise<coresdk.nexus.INexusTaskCompletion> {
try {
const input = await decodeFromPayload(this.dataConverter, payload);
Copy link
Contributor

@dandavison dandavison May 13, 2025

Choose a reason for hiding this comment

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

If the data converter fails here, I assume it should result in a 400 BAD_REQUEST Nexus HTTP response, right? If that's correct can you confirm that it does?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's a test worth adding. Thanks!

bergundy added 8 commits May 14, 2025 20:02
- This PR is blocked on publishing an initial version of the `nexus-rpc` package.
- There is a TODO to figure out HandlerError and OperationError message rehydration, pending discussion.
- Interceptors not yet implemented.
- `WorkflowRunOperation` and `getClient()` not implemented for the `@temporalio/nexus` package.
- Tests use the HTTP API directly in lieu of a workflow caller or strongly typed client, we can refactor those later.
}
if (!token.wid) {
throw new TypeError('invalid workflow run token: missing workflow ID (wid)');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I see that Go and TS don't validate the 'ns' field. Is that deliberate?

@mjameswh mjameswh force-pushed the nexus-cp-1 branch 2 times, most recently from edf39b3 to d5bf6ca Compare August 21, 2025 15:36
@mjameswh mjameswh deleted the branch temporalio:main August 21, 2025 16:11
@mjameswh mjameswh closed this Aug 21, 2025
@mjameswh mjameswh reopened this Aug 21, 2025
@mjameswh mjameswh changed the base branch from nexus-cp-1 to nexus-cp-3 August 21, 2025 17:30
@mjameswh mjameswh deleted the branch temporalio:main August 21, 2025 18:37
@mjameswh mjameswh closed this Aug 21, 2025
@mjameswh mjameswh reopened this Aug 21, 2025
@mjameswh mjameswh changed the base branch from nexus-cp-3 to main August 21, 2025 18:41
@mjameswh mjameswh changed the title feat(nexus): Nexus support feat(nexus): Initial Nexus support Aug 21, 2025
Copy link
Contributor

@THardy98 THardy98 left a comment

Choose a reason for hiding this comment

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

leaving this for now, but haven't finished my review


Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/).

- [API reference](https://typescript.temporal.io/api/namespaces/nexus)
Copy link
Contributor

Choose a reason for hiding this comment

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

This link 404s atm

Copy link
Contributor

Choose a reason for hiding this comment

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

API docs will get published once this PR is merged to main. The link will remain a 404 till then.

@@ -0,0 +1,14 @@
export {
//
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's intentional, to prevent Prettier from collapsing the list of reexported members into a single line. That makes it easier to review code diffs that affect the public API of our packages.

There are surely better ways to achieve that, but that one works fine just now.

Comment on lines +19 to +32
executeOperation<O extends T['operations'][keyof T['operations']]>(
op: O,
input: nexus.OperationInput<O>,
options?: Partial<StartNexusOperationOptions>
): Promise<nexus.OperationOutput<O>>;

/**
* Start a Nexus Operation and wait for its completion taking an operation name.
*/
executeOperation<K extends nexus.OperationKey<T['operations']>>(
op: K,
input: nexus.OperationInput<T['operations'][K]>,
options?: Partial<StartNexusOperationOptions>
): Promise<nexus.OperationOutput<T['operations'][K]>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there reasonable benefit to have an overload for this where the user provides the key instead of the direct operation? (same for the start calls)

The benefit of this is not clear to me, but I'm not as familiar with the nexus-rpc semantics

Copy link
Contributor

Choose a reason for hiding this comment

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

As it is now, a user may do either of these:

  client.executeOperation(service.someOperation, ...)

or

  client.executeOperation('someOperation', ...)

Support for both forms is a requirement.

Comment on lines +696 to +697
// It is possible for ResolveNexusOperation to be received without a prior ResolveNexusOperationStart,
// e.g. either because the handler completed the operation synchronously.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify this case?
If the handler completes the operation synchronously, is no start activation ever emit?

Copy link
Contributor

Choose a reason for hiding this comment

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

The Nexus spec allows a remote service to complete either synchronously or asynchronously (for the purpose of this discussion, synchronicity is from the perspective of the Temporal server, as it is the one making a network request to that remote service).

If the service responds asynchronously, the Temporal server will record a NexusOperationStartedEvent in the workflow history, and later append a NexusOperationCompletedEvent to indicate actual completion. If the service responds synchronously, the Temporal server will only write a NexusOperationCompletedEvent, thus saving one event in the workflow history.

Core SDK could possibly fill the gap for the missing NexusOperationStartedEvent, but it was decided not to do so, so we have to handle both of those scenarios here.

*
* @experimental Nexus support in Temporal SDK is experimental.
*/
export interface StartNexusOperationInput {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this ever user-facing? Do the fields need docstrings?

Comment on lines +174 to +181
const completed =
!activator.completions.nexusOperationStart.has(seq) &&
!activator.completions.nexusOperationComplete.has(seq);

if (!completed) {
activator.pushCommand({
requestCancelNexusOperation: { seq },
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just check nexusOperationComplete ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because cancellation could also be requested for an operation that is still in the "Start" state.

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.

4 participants