Skip to content

NativeConnection powered Client #1699

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

Merged

Conversation

bergundy
Copy link
Member

This work enables both Nexus handlers and activities to get a Client powered by the NativeConnection of the Worker they are run in.

I left a TODO to support request cancelation via withAbortSignal, I may wait for @mjameswh's refactor before adding that.

@bergundy bergundy requested a review from a team as a code owner April 30, 2025 00:10
@bergundy bergundy force-pushed the native-connection-powered-client branch 2 times, most recently from c8121c2 to 9accec3 Compare April 30, 2025 00:17
@bergundy
Copy link
Member Author

I'm adding tests for using the client including the TransportError to ServiceError conversion.

@bergundy bergundy force-pushed the native-connection-powered-client branch 4 times, most recently from 4c05e3a to b36f850 Compare May 1, 2025 16:25
/**
* nativeClient is intentionally left private, framework code can access it with `extractNativeClient` (below)
*/
protected constructor(private nativeClient: Client) {}
protected constructor(private nativeClient: Client) {
this.workflowService = WorkflowService.create(this.sendRequest.bind(this), false, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely not required for this PR, but any reason we should not also implement other services over NativeConnection at some later point?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can definitely do that later, I just wanted to limit the scope for this MVP. I will open an issue.

@@ -1,29 +1,208 @@
import util from 'node:util';
import { AsyncLocalStorage } from 'node:async_hooks';
import * as grpc from '@grpc/grpc-js';
import { getRelativeTimeout } from '@grpc/grpc-js/build/src/deadline';
Copy link
Contributor

Choose a reason for hiding this comment

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

That function is pretty simple. I'd prefer we inline it here rather than have an import to their internal project structure.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do.

import { NativeConnectionOptions } from './connection-options';
import { Runtime } from './runtime';

const updateHeaders = util.promisify(clientUpdateHeaders);
const updateApiKey = util.promisify(clientUpdateApiKey);

@SymbolBasedInstanceOfError('ServiceError')
export class ServiceError extends Error {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should export this class, nor use the @SymbolBasedInstanceOfError decorator on it, as it is likely to be confused with the ServiceError class exported by the client package (yet, isn't exactly the same thing, as the later corresponds to errors rethrown by the Client, not those coming from the Connection).

In the client package, we simply use the isGrpcServiceError() type guard. So I suggest you keep this class here, just make it private and remove the decorator, and possibly add an implements GrpcServiceError if you feel like it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm... I feel like any error that we throw should be exported but I'm not highly opinionated here.
FYI grpc-js ServiceError is not a concrete implementation, which is why I created this class.

WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like any error that we throw should be exported

That's normally what I would do, but in this case, NativeConnection is basically mocking the grpc-js API, which isn't exposing that class.

Exposing that class from the worker package means that a user (or our own code) could do err instanceof ServiceError if the error come from a NativeConnection, but would imperatively have to do isGrpcServiceError if the connection is a Connection.

Copy link
Contributor

Choose a reason for hiding this comment

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

And indeed, grpc-js's ServiceError is just a type, not a class, so you would still want that class internally; just add an implement GrpcServiceError to the class signature to ensure we're not deviating from their API.

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 ended up not exporting this since it's easy to export later while we cannot remove an already exported class.

Copy link
Contributor

@mjameswh mjameswh left a comment

Choose a reason for hiding this comment

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

Left a few important comments, but approving.

@bergundy bergundy force-pushed the native-connection-powered-client branch from b36f850 to 158107c Compare May 1, 2025 19:53
@bergundy bergundy merged commit 8c7506b into temporalio:main May 1, 2025
23 checks passed
@bergundy bergundy deleted the native-connection-powered-client branch May 1, 2025 21:13
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.

2 participants