11import { status } from '@grpc/grpc-js' ;
22import { v4 as uuid4 } from 'uuid' ;
33import type {
4+ ActivityFunction ,
45 LoadedDataConverter ,
56 Next ,
67 Priority ,
@@ -31,6 +32,7 @@ import {
3132 encodeUserMetadata ,
3233} from '@temporalio/common/lib/internal-non-workflow' ;
3334import type { temporal } from '@temporalio/proto' ;
35+ import type { Replace } from '@temporalio/common/lib/type-helpers' ;
3436import type {
3537 ActivityCancelInput ,
3638 ActivityClientInterceptor ,
@@ -73,7 +75,7 @@ export interface ActivityClientOptions extends AsyncCompletionClientOptions {
7375 * Typically this client should not be instantiated directly, instead create the high level {@link Client} and use
7476 * {@link Client.activity} to interact with Activities.
7577 */
76- export class ActivityClient extends AsyncCompletionClient {
78+ export class ActivityClient extends AsyncCompletionClient implements TypedActivityClient < any > {
7779 private readonly interceptedHandlers : {
7880 [ K in keyof Required < ActivityClientInterceptor > ] : Next < ActivityClientInterceptor , K > ;
7981 } ;
@@ -93,14 +95,25 @@ export class ActivityClient extends AsyncCompletionClient {
9395 } ;
9496 }
9597
98+ /**
99+ * Returns this client as a {@link TypedActivityClient}. It enables strong type checking of Activity name, arguments
100+ * and result based on the provided Activity interface. Note that no new client object is created - this method only
101+ * affects type annotations.
102+ * @template T Activity interface to use for type checking. The returned client can only start activities present in
103+ * this interface.
104+ */
105+ typedClient < T > ( ) : TypedActivityClient < T > {
106+ return this ;
107+ }
108+
96109 /**
97110 * Starts new Standalone Activity execution.
98111 *
99112 * @param activity Name of the activity to start.
100113 * @param options Options controlling the start and execution of the activity.
101114 * @returns Handle to the started activity. The handle's `runId` property will be set to the started run.
102115 */
103- async start < O = any > ( activity : string , options : ActivityOptions ) : Promise < ActivityHandle < O > > {
116+ async start < R = any > ( activity : string , options : ActivityOptions ) : Promise < ActivityHandle < R > > {
104117 return this . interceptedHandlers . start ( {
105118 activityType : activity ,
106119 options,
@@ -114,7 +127,7 @@ export class ActivityClient extends AsyncCompletionClient {
114127 * @param options Options controlling the activity execution.
115128 * @returns Result of the activity.
116129 */
117- async execute < O = any > ( activity : string , options : ActivityOptions ) : Promise < O > {
130+ async execute < R = any > ( activity : string , options : ActivityOptions ) : Promise < R > {
118131 const handle = await this . start ( activity , options ) ;
119132 return handle . result ( ) ;
120133 }
@@ -134,7 +147,7 @@ export class ActivityClient extends AsyncCompletionClient {
134147 * @param runId Optional run ID of the specific Activity execution.
135148 * @returns Handle to the specified activity execution.
136149 */
137- getHandle < O = any > ( activityId : string , runId ?: string ) : ActivityHandle < O > {
150+ getHandle < R = any > ( activityId : string , runId ?: string ) : ActivityHandle < R > {
138151 return this . createHandle ( activityId , runId ) ;
139152 }
140153
@@ -168,7 +181,7 @@ export class ActivityClient extends AsyncCompletionClient {
168181 } ) ;
169182 }
170183
171- protected createHandle < O > ( activityId : string , runId ?: string ) : ActivityHandle < O > {
184+ protected createHandle < R > ( activityId : string , runId ?: string ) : ActivityHandle < R > {
172185 if ( ! activityId ) {
173186 throw new TypeError ( 'activityId is required' ) ;
174187 }
@@ -178,7 +191,7 @@ export class ActivityClient extends AsyncCompletionClient {
178191 activityId,
179192 runId,
180193
181- async result ( ) : Promise < O > {
194+ async result ( ) : Promise < R > {
182195 return await this . client . interceptedHandlers . getResult ( {
183196 activityId : this . activityId ,
184197 activityRunId : this . runId ?? '' ,
@@ -409,8 +422,9 @@ export class ActivityClient extends AsyncCompletionClient {
409422/**
410423 * Handle that can be used to perform operations on the associated Activity.
411424 * Can be obtained by calling {@link ActivityClient.start} or {@link ActivityClient.getHandle}.
425+ * @template R Result type of the activity. Use {@link ActivityClient.typedClient} to start activities in a type-safe way.
412426 */
413- export interface ActivityHandle < O = any > {
427+ export interface ActivityHandle < R = any > {
414428 /**
415429 * ID of the Activity this handle refers to.
416430 */
@@ -425,7 +439,7 @@ export interface ActivityHandle<O = any> {
425439 * If the activity was not successful, throws {@link ActivityExecutionFailedError}. The activity failure is stored in
426440 * the `cause` field.
427441 */
428- result ( ) : Promise < O > ;
442+ result ( ) : Promise < R > ;
429443 /**
430444 * Returns information about the Activity execution.
431445 */
@@ -455,7 +469,7 @@ export interface ActivityOptions {
455469 /**
456470 * Input arguments to pass to the activity.
457471 */
458- args ?: any [ ] ;
472+ args ?: any [ ] | Readonly < any [ ] > ;
459473 /**
460474 * If set, specifies maximum time between successful heartbeats.
461475 */
@@ -579,3 +593,76 @@ function buildActivityDescription(
579593 getLastFailure,
580594 } ;
581595}
596+
597+ /**
598+ * Sub-interface of {@link ActivityClient} that provides a strongly-typed interface for executing Activities.
599+ * Argument types in the provided options must match the argument types of the specified Activity as defined in provided
600+ * interface
601+ * @template T Activity interface
602+ */
603+ export interface TypedActivityClient < T > {
604+ start < N extends ActivityName < T > > (
605+ activity : N ,
606+ options : ActivityOptionsFor < T , N >
607+ ) : Promise < ActivityHandle < ActivityResult < T , N > > > ;
608+
609+ execute < N extends ActivityName < T > > ( activity : N , options : ActivityOptionsFor < T , N > ) : Promise < ActivityResult < T , N > > ;
610+ }
611+
612+ /**
613+ * Utility type to support strong typing in {@link TypedActivityClient}.
614+ * Contains names of activities extracted from the specified activity interface.
615+ * @template T Activity interface
616+ */
617+ export type ActivityName < T > = {
618+ [ N in keyof T & string ] : T [ N ] extends ActivityFunction < any , any > ? N : never ;
619+ } [ keyof T & string ] ;
620+
621+ /**
622+ * Utility type to support strong typing in {@link TypedActivityClient}.
623+ * Extracts argument types of an activity.
624+ * @template T Activity interface
625+ * @template N Activity name
626+ */
627+ export type ActivityArgs < T , N extends ActivityName < T > > = T [ N ] extends ActivityFunction < infer P , any > ? P : never ;
628+
629+ /**
630+ * Utility type to support strong typing in {@link TypedActivityClient}.
631+ * Extracts result type of an activity.
632+ * @template T Activity interface
633+ * @template N Activity name
634+ */
635+ export type ActivityResult < T , N extends ActivityName < T > > = T [ N ] extends ActivityFunction < any , infer R > ? R : never ;
636+
637+ /**
638+ * Utility type to support strong typing in {@link TypedActivityClient}.
639+ * Represents {@link ActivityOptions} with strongly typed arguments.
640+ * @template Args Types of activity arguments as an array type.
641+ */
642+ export type ActivityOptionsWithArgs < Args extends any [ ] > = Args extends [ any , ...any ]
643+ ? Replace <
644+ ActivityOptions ,
645+ {
646+ /**
647+ * Arguments to pass to the Activity
648+ */
649+ args : Args | Readonly < Args > ;
650+ }
651+ >
652+ : Replace <
653+ ActivityOptions ,
654+ {
655+ /**
656+ * Arguments to pass to the Activity
657+ */
658+ args ?: Args | Readonly < Args > ;
659+ }
660+ > ;
661+
662+ /**
663+ * Utility type to support strong typing in {@link TypedActivityClient}.
664+ * Represents {@link ActivityOptions} with strongly typed arguments matching specified Activity in specified interface.
665+ * @template T Activity interface
666+ * @template N Activity name
667+ */
668+ export type ActivityOptionsFor < T , N extends ActivityName < T > > = ActivityOptionsWithArgs < ActivityArgs < T , N > > ;
0 commit comments