Skip to content

Commit 1df75b0

Browse files
authored
Expose Client on Activity Context (#1769)
1 parent 27e855c commit 1df75b0

29 files changed

+717
-245
lines changed

packages/activity/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"author": "Temporal Technologies Inc. <[email protected]>",
1414
"license": "MIT",
1515
"dependencies": {
16+
"@temporalio/client": "file:../client",
1617
"@temporalio/common": "file:../common",
1718
"abort-controller": "^3.0.0"
1819
},

packages/activity/src/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ import {
7878
MetricMeter,
7979
Priority,
8080
ActivityCancellationDetails,
81+
IllegalStateError,
8182
} from '@temporalio/common';
8283
import { msToNumber } from '@temporalio/common/lib/time';
8384
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
8485
import { ActivityCancellationDetailsHolder } from '@temporalio/common/lib/activity-cancellation-details';
86+
import { Client } from '@temporalio/client';
8587

8688
export {
8789
ActivityFunction,
@@ -273,6 +275,11 @@ export class Context {
273275
*/
274276
protected readonly heartbeatFn: (details?: any) => void;
275277

278+
/**
279+
* The Worker's client, passed down through Activity context.
280+
*/
281+
protected readonly _client: Client | undefined;
282+
276283
/**
277284
* The logger for this Activity.
278285
*
@@ -313,6 +320,7 @@ export class Context {
313320
cancelled: Promise<never>,
314321
cancellationSignal: AbortSignal,
315322
heartbeat: (details?: any) => void,
323+
client: Client | undefined,
316324
log: Logger,
317325
metricMeter: MetricMeter,
318326
details: ActivityCancellationDetailsHolder
@@ -321,6 +329,7 @@ export class Context {
321329
this.cancelled = cancelled;
322330
this.cancellationSignal = cancellationSignal;
323331
this.heartbeatFn = heartbeat;
332+
this._client = client;
324333
this.log = log;
325334
this.metricMeter = metricMeter;
326335
this._cancellationDetails = details;
@@ -351,6 +360,25 @@ export class Context {
351360
this.heartbeatFn(details);
352361
};
353362

363+
/**
364+
* A Temporal Client, bound to the same Temporal Namespace as the Worker executing this Activity.
365+
*
366+
* May throw an {@link IllegalStateError} if the Activity is running inside a `MockActivityEnvironment`
367+
* that was created without a Client.
368+
*
369+
* @experimental Client support over `NativeConnection` is experimental. Error handling may be
370+
* incomplete or different from what would be observed using a {@link Connection}
371+
* instead. Client doesn't support cancellation through a Signal.
372+
*/
373+
public get client(): Client {
374+
if (this._client === undefined) {
375+
throw new IllegalStateError(
376+
'No Client available. This may be a MockActivityEnvironment that was created without a Client.'
377+
);
378+
}
379+
return this._client;
380+
}
381+
354382
/**
355383
* Helper function for sleeping in an Activity.
356384
* @param ms Sleep duration: number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}
@@ -481,6 +509,22 @@ export function cancellationSignal(): AbortSignal {
481509
return Context.current().cancellationSignal;
482510
}
483511

512+
/**
513+
* A Temporal Client, bound to the same Temporal Namespace as the Worker executing this Activity.
514+
*
515+
* May throw an {@link IllegalStateError} if the Activity is running inside a `MockActivityEnvironment`
516+
* that was created without a Client.
517+
*
518+
* This is a shortcut for `Context.current().client` (see {@link Context.client}).
519+
*
520+
* @experimental Client support over `NativeConnection` is experimental. Error handling may be
521+
* incomplete or different from what would be observed using a {@link Connection}
522+
* instead. Client doesn't support cancellation through a Signal.
523+
*/
524+
export function getClient(): Client {
525+
return Context.current().client;
526+
}
527+
484528
/**
485529
* Get the metric meter for the current activity, with activity-specific tags.
486530
*

packages/activity/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"outDir": "./lib",
55
"rootDir": "./src"
66
},
7-
"references": [{ "path": "../common" }],
7+
"references": [{ "path": "../common" }, { "path": "../client" }],
88
"include": ["./src/**/*.ts"]
99
}

packages/client/src/base-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function defaultBaseClientOptions(): WithDefaults<BaseClientOptions> {
5353

5454
export class BaseClient {
5555
/**
56-
* The underlying {@link Connection | connection} used by this client.
56+
* The underlying {@link Connection | connection} or {@link NativeConnection | native connection} used by this client.
5757
*
5858
* Clients are cheap to create, but connections are expensive. Where it makes sense,
5959
* a single connection may and should be reused by multiple `Client`s.

packages/client/src/connection.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { type temporal } from '@temporalio/proto';
1212
import { isGrpcServiceError, ServiceError } from './errors';
1313
import { defaultGrpcRetryOptions, makeGrpcRetryInterceptor } from './grpc-retry';
1414
import pkg from './pkg';
15-
import { CallContext, HealthService, Metadata, OperatorService, WorkflowService } from './types';
15+
import { CallContext, HealthService, Metadata, OperatorService, TestService, WorkflowService } from './types';
1616

1717
/**
1818
* The default Temporal Server's TCP port for public gRPC connections.
@@ -138,6 +138,23 @@ export type ConnectionOptionsWithDefaults = Required<
138138
connectTimeoutMs: number;
139139
};
140140

141+
/**
142+
* A symbol used to attach extra, SDK-internal connection options.
143+
*
144+
* @internal
145+
* @hidden
146+
*/
147+
export const InternalConnectionOptionsSymbol = Symbol('__temporal_internal_connection_options');
148+
export type InternalConnectionOptions = ConnectionOptions & {
149+
[InternalConnectionOptionsSymbol]?: {
150+
/**
151+
* Indicate whether the `TestService` should be enabled on this connection. This is set to true
152+
* on connections created internally by `TestWorkflowEnvironment.createTimeSkipping()`.
153+
*/
154+
supportsTestService?: boolean;
155+
};
156+
};
157+
141158
export const LOCAL_TARGET = 'localhost:7233';
142159

143160
function addDefaults(options: ConnectionOptions): ConnectionOptionsWithDefaults {
@@ -239,6 +256,13 @@ export interface ConnectionCtorOptions {
239256
*/
240257
readonly operatorService: OperatorService;
241258

259+
/**
260+
* Raw gRPC access to the Temporal test service.
261+
*
262+
* Will be `undefined` if connected to a server that does not support the test service.
263+
*/
264+
readonly testService: TestService | undefined;
265+
242266
/**
243267
* Raw gRPC access to the standard gRPC {@link https://github.com/grpc/grpc/blob/92f58c18a8da2728f571138c37760a721c8915a2/doc/health-checking.md | health service}.
244268
*/
@@ -286,6 +310,13 @@ export class Connection {
286310
*/
287311
public readonly operatorService: OperatorService;
288312

313+
/**
314+
* Raw gRPC access to the Temporal test service.
315+
*
316+
* Will be `undefined` if connected to a server that does not support the test service.
317+
*/
318+
public readonly testService: TestService | undefined;
319+
289320
/**
290321
* Raw gRPC access to the standard gRPC {@link https://github.com/grpc/grpc/blob/92f58c18a8da2728f571138c37760a721c8915a2/doc/health-checking.md | health service}.
291322
*/
@@ -326,6 +357,7 @@ export class Connection {
326357
apiKeyFnRef,
327358
});
328359
const workflowService = WorkflowService.create(workflowRpcImpl, false, false);
360+
329361
const operatorRpcImpl = this.generateRPCImplementation({
330362
serviceName: 'temporal.api.operatorservice.v1.OperatorService',
331363
client,
@@ -335,6 +367,20 @@ export class Connection {
335367
apiKeyFnRef,
336368
});
337369
const operatorService = OperatorService.create(operatorRpcImpl, false, false);
370+
371+
let testService: TestService | undefined = undefined;
372+
if ((options as InternalConnectionOptions)?.[InternalConnectionOptionsSymbol]?.supportsTestService) {
373+
const testRpcImpl = this.generateRPCImplementation({
374+
serviceName: 'temporal.api.testservice.v1.TestService',
375+
client,
376+
callContextStorage,
377+
interceptors: optionsWithDefaults?.interceptors,
378+
staticMetadata: optionsWithDefaults.metadata,
379+
apiKeyFnRef,
380+
});
381+
testService = TestService.create(testRpcImpl, false, false);
382+
}
383+
338384
const healthRpcImpl = this.generateRPCImplementation({
339385
serviceName: 'grpc.health.v1.Health',
340386
client,
@@ -350,6 +396,7 @@ export class Connection {
350396
callContextStorage,
351397
workflowService,
352398
operatorService,
399+
testService,
353400
healthService,
354401
options: optionsWithDefaults,
355402
apiKeyFnRef,
@@ -414,6 +461,7 @@ export class Connection {
414461
client,
415462
workflowService,
416463
operatorService,
464+
testService,
417465
healthService,
418466
callContextStorage,
419467
apiKeyFnRef,
@@ -422,6 +470,7 @@ export class Connection {
422470
this.client = client;
423471
this.workflowService = this.withNamespaceHeaderInjector(workflowService);
424472
this.operatorService = operatorService;
473+
this.testService = testService;
425474
this.healthService = healthService;
426475
this.callContextStorage = callContextStorage;
427476
this.apiKeyFnRef = apiKeyFnRef;

packages/client/src/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export type WorkflowService = proto.temporal.api.workflowservice.v1.WorkflowServ
9191
export const { WorkflowService } = proto.temporal.api.workflowservice.v1;
9292
export type OperatorService = proto.temporal.api.operatorservice.v1.OperatorService;
9393
export const { OperatorService } = proto.temporal.api.operatorservice.v1;
94+
export type TestService = proto.temporal.api.testservice.v1.TestService;
95+
export const { TestService } = proto.temporal.api.testservice.v1;
9496
export type HealthService = proto.grpc.health.v1.Health;
9597
export const { Health: HealthService } = proto.grpc.health.v1;
9698

@@ -117,9 +119,6 @@ export interface CallContext {
117119

118120
/**
119121
* Connection interface used by high level SDK clients.
120-
*
121-
* NOTE: Currently the SDK only supports grpc-js based connection but in the future
122-
* we might support grpc-web and native Rust connections.
123122
*/
124123
export interface ConnectionLike {
125124
workflowService: WorkflowService;

0 commit comments

Comments
 (0)