-
Notifications
You must be signed in to change notification settings - Fork 130
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
base: main
Are you sure you want to change the base?
Conversation
test.beforeEach(async (t) => { | ||
const taskQueue = t.title + randomUUID(); | ||
const { env } = t.context; | ||
const response = await env.connection.operatorService.createNexusEndpoint({ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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!
packages/worker/src/nexus.ts
Outdated
options: nexus.StartOperationOptions | ||
): Promise<coresdk.nexus.INexusTaskCompletion> { | ||
try { | ||
const input = await decodeFromPayload(this.dataConverter, payload); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
- 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)'); | ||
} |
There was a problem hiding this comment.
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?
edf39b3
to
d5bf6ca
Compare
There was a problem hiding this 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This link 404s atm
There was a problem hiding this comment.
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 { | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove?
There was a problem hiding this comment.
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.
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]>>; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
// It is possible for ResolveNexusOperation to be received without a prior ResolveNexusOperationStart, | ||
// e.g. either because the handler completed the operation synchronously. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
const completed = | ||
!activator.completions.nexusOperationStart.has(seq) && | ||
!activator.completions.nexusOperationComplete.has(seq); | ||
|
||
if (!completed) { | ||
activator.pushCommand({ | ||
requestCancelNexusOperation: { seq }, | ||
}); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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
HandlerError
andOperationError
serialization. In the mean time, this PR mostly adheres to the original spec (i.e. a Nexus should have either amessage
or acause
), but little effort has been put into compliance testing, as we know the present model is broken in certain aspects.