Skip to content

Commit edf39b3

Browse files
committed
Expose Client on Activity Context + impl all services over NativeConnection
1 parent 27e855c commit edf39b3

29 files changed

+774
-323
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: 102 additions & 79 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,
@@ -238,93 +240,79 @@ export class Context {
238240
}
239241

240242
/**
241-
* Holds information about the current executing Activity.
242-
*/
243-
public readonly info: Info;
244-
245-
/**
246-
* A Promise that fails with a {@link CancelledFailure} when cancellation of this activity is requested. The promise
247-
* is guaranteed to never successfully resolve. Await this promise in an Activity to get notified of cancellation.
248-
*
249-
* Note that to get notified of cancellation, an activity must _also_ {@link Context.heartbeat}.
243+
* **Not** meant to instantiated by Activity code, used by the worker.
250244
*
251-
* @see [Cancellation](/api/namespaces/activity#cancellation)
245+
* @ignore
252246
*/
253-
public readonly cancelled: Promise<never>;
247+
constructor(
248+
/**
249+
* Holds information about the current executing Activity.
250+
*/
251+
public readonly info: Info,
254252

255-
/**
256-
* An {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that can be used to react to
257-
* Activity cancellation.
258-
*
259-
* This can be passed in to libraries such as
260-
* {@link https://www.npmjs.com/package/node-fetch#request-cancellation-with-abortsignal | fetch} to abort an
261-
* in-progress request and
262-
* {@link https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options child_process}
263-
* to abort a child process, as well as other built-in node modules and modules found on npm.
264-
*
265-
* Note that to get notified of cancellation, an activity must _also_ {@link Context.heartbeat}.
266-
*
267-
* @see [Cancellation](/api/namespaces/activity#cancellation)
268-
*/
269-
public readonly cancellationSignal: AbortSignal;
253+
/**
254+
* A Promise that fails with a {@link CancelledFailure} when cancellation of this activity is requested. The promise
255+
* is guaranteed to never successfully resolve. Await this promise in an Activity to get notified of cancellation.
256+
*
257+
* Note that to get notified of cancellation, an activity must _also_ {@link Context.heartbeat}.
258+
*
259+
* @see [Cancellation](/api/namespaces/activity#cancellation)
260+
*/
261+
public readonly cancelled: Promise<never>,
270262

271-
/**
272-
* The heartbeat implementation, injected via the constructor.
273-
*/
274-
protected readonly heartbeatFn: (details?: any) => void;
263+
/**
264+
* An {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that can be used to react to
265+
* Activity cancellation.
266+
*
267+
* This can be passed in to libraries such as
268+
* {@link https://www.npmjs.com/package/node-fetch#request-cancellation-with-abortsignal | fetch} to abort an
269+
* in-progress request and
270+
* {@link https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options child_process}
271+
* to abort a child process, as well as other built-in node modules and modules found on npm.
272+
*
273+
* Note that to get notified of cancellation, an activity must _also_ {@link Context.heartbeat}.
274+
*
275+
* @see [Cancellation](/api/namespaces/activity#cancellation)
276+
*/
277+
public readonly cancellationSignal: AbortSignal,
275278

276-
/**
277-
* The logger for this Activity.
278-
*
279-
* This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the current Activity context
280-
* are automatically included as metadata on every log entries. An extra `sdkComponent` metadata attribute is also
281-
* added, with value `activity`; this can be used for fine-grained filtering of log entries further downstream.
282-
*
283-
* To customize log attributes, register a {@link ActivityOutboundCallsInterceptor} that intercepts the
284-
* `getLogAttributes()` method.
285-
*
286-
* Modifying the context logger (eg. `context.log = myCustomLogger` or by an {@link ActivityInboundLogInterceptor}
287-
* with a custom logger as argument) is deprecated. Doing so will prevent automatic inclusion of custom log attributes
288-
* through the `getLogAttributes()` interceptor. To customize _where_ log messages are sent, set the
289-
* {@link Runtime.logger} property instead.
290-
*/
291-
public log: Logger;
279+
/**
280+
* The heartbeat implementation, injected via the constructor.
281+
*/
282+
protected readonly heartbeatFn: (details?: any) => void,
292283

293-
/**
294-
* Get the metric meter for this activity with activity-specific tags.
295-
*
296-
* To add custom tags, register a {@link ActivityOutboundCallsInterceptor} that
297-
* intercepts the `getMetricTags()` method.
298-
*/
299-
public readonly metricMeter: MetricMeter;
284+
private readonly _client: Client | undefined,
300285

301-
/**
302-
* Holder object for activity cancellation details
303-
*/
304-
private readonly _cancellationDetails: ActivityCancellationDetailsHolder;
286+
/**
287+
* The logger for this Activity.
288+
*
289+
* This defaults to the `Runtime`'s Logger (see {@link Runtime.logger}). Attributes from the current Activity context
290+
* are automatically included as metadata on every log entries. An extra `sdkComponent` metadata attribute is also
291+
* added, with value `activity`; this can be used for fine-grained filtering of log entries further downstream.
292+
*
293+
* To customize log attributes, register a {@link ActivityOutboundCallsInterceptor} that intercepts the
294+
* `getLogAttributes()` method.
295+
*
296+
* Modifying the context logger (eg. `context.log = myCustomLogger` or by an {@link ActivityInboundLogInterceptor}
297+
* with a custom logger as argument) is deprecated. Doing so will prevent automatic inclusion of custom log attributes
298+
* through the `getLogAttributes()` interceptor. To customize _where_ log messages are sent, set the
299+
* {@link Runtime.logger} property instead.
300+
*/
301+
public log: Logger,
305302

306-
/**
307-
* **Not** meant to instantiated by Activity code, used by the worker.
308-
*
309-
* @ignore
310-
*/
311-
constructor(
312-
info: Info,
313-
cancelled: Promise<never>,
314-
cancellationSignal: AbortSignal,
315-
heartbeat: (details?: any) => void,
316-
log: Logger,
317-
metricMeter: MetricMeter,
318-
details: ActivityCancellationDetailsHolder
319-
) {
320-
this.info = info;
321-
this.cancelled = cancelled;
322-
this.cancellationSignal = cancellationSignal;
323-
this.heartbeatFn = heartbeat;
324-
this.log = log;
325-
this.metricMeter = metricMeter;
326-
this._cancellationDetails = details;
327-
}
303+
/**
304+
* Get the metric meter for this activity with activity-specific tags.
305+
*
306+
* To add custom tags, register a {@link ActivityOutboundCallsInterceptor} that
307+
* intercepts the `getMetricTags()` method.
308+
*/
309+
public readonly metricMeter: MetricMeter,
310+
311+
/**
312+
* Holder object for activity cancellation details
313+
*/
314+
private readonly _cancellationDetails: ActivityCancellationDetailsHolder
315+
) {}
328316

329317
/**
330318
* Send a {@link https://docs.temporal.io/concepts/what-is-an-activity-heartbeat | heartbeat} from an Activity.
@@ -351,6 +339,25 @@ export class Context {
351339
this.heartbeatFn(details);
352340
};
353341

342+
/**
343+
* A Temporal Client, bound to the same Temporal Namespace as the Worker executing this Activity.
344+
*
345+
* May throw an {@link IllegalStateError} if the Activity is running inside a `MockActivityEnvironment`
346+
* that was created without a Client.
347+
*
348+
* @experimental Client support over `NativeConnection` is experimental. Error handling may be
349+
* incomplete or different from what would be observed using a {@link Connection}
350+
* instead. Client doesn't support cancellation through a Signal.
351+
*/
352+
public get client(): Client {
353+
if (this._client === undefined) {
354+
throw new IllegalStateError(
355+
'No Client available. This may be a MockActivityEnvironment that was created without a Client.'
356+
);
357+
}
358+
return this._client;
359+
}
360+
354361
/**
355362
* Helper function for sleeping in an Activity.
356363
* @param ms Sleep duration: number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}
@@ -481,6 +488,22 @@ export function cancellationSignal(): AbortSignal {
481488
return Context.current().cancellationSignal;
482489
}
483490

491+
/**
492+
* A Temporal Client, bound to the same Temporal Namespace as the Worker executing this Activity.
493+
*
494+
* May throw an {@link IllegalStateError} if the Activity is running inside a `MockActivityEnvironment`
495+
* that was created without a Client.
496+
*
497+
* This is a shortcut for `Context.current().client` (see {@link Context.client}).
498+
*
499+
* @experimental Client support over `NativeConnection` is experimental. Error handling may be
500+
* incomplete or different from what would be observed using a {@link Connection}
501+
* instead. Client doesn't support cancellation through a Signal.
502+
*/
503+
export function getClient(): Client {
504+
return Context.current().client;
505+
}
506+
484507
/**
485508
* Get the metric meter for the current activity, with activity-specific tags.
486509
*

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)