@temporalio/client 1.11.5 → 1.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ import {
23
23
  encodeWorkflowIdReusePolicy,
24
24
  decodeRetryState,
25
25
  encodeWorkflowIdConflictPolicy,
26
+ WorkflowIdConflictPolicy,
26
27
  } from '@temporalio/common';
27
28
  import { composeInterceptors } from '@temporalio/common/lib/interceptors';
28
29
  import { History } from '@temporalio/common/lib/proto-utils';
@@ -56,8 +57,11 @@ import {
56
57
  WorkflowTerminateInput,
57
58
  WorkflowStartUpdateInput,
58
59
  WorkflowStartUpdateOutput,
60
+ WorkflowStartUpdateWithStartInput,
61
+ WorkflowStartUpdateWithStartOutput,
59
62
  } from './interceptors';
60
63
  import {
64
+ CountWorkflowExecution,
61
65
  DescribeWorkflowExecutionResponse,
62
66
  encodeQueryRejectCondition,
63
67
  GetWorkflowExecutionHistoryRequest,
@@ -77,7 +81,7 @@ import {
77
81
  WorkflowStartOptions,
78
82
  WorkflowUpdateOptions,
79
83
  } from './workflow-options';
80
- import { executionInfoFromRaw, rethrowKnownErrorTypes } from './helpers';
84
+ import { decodeCountWorkflowExecutionsResponse, executionInfoFromRaw, rethrowKnownErrorTypes } from './helpers';
81
85
  import {
82
86
  BaseClient,
83
87
  BaseClientOptions,
@@ -124,8 +128,6 @@ export interface WorkflowHandle<T extends Workflow = Workflow> extends BaseWorkf
124
128
  /**
125
129
  * Start an Update and wait for the result.
126
130
  *
127
- * @experimental Update is an experimental feature.
128
- *
129
131
  * @throws {@link WorkflowUpdateFailedError} if Update validation fails or if ApplicationFailure is thrown in the Update handler.
130
132
  * @throws {@link WorkflowUpdateRPCTimeoutOrCancelledError} if this Update call timed out or was cancelled. This doesn't
131
133
  * mean the update itself was timed out or cancelled.
@@ -151,8 +153,6 @@ export interface WorkflowHandle<T extends Workflow = Workflow> extends BaseWorkf
151
153
  * Start an Update and receive a handle to the Update. The Update validator (if present) is run
152
154
  * before the handle is returned.
153
155
  *
154
- * @experimental Update is an experimental feature.
155
- *
156
156
  * @throws {@link WorkflowUpdateFailedError} if Update validation fails.
157
157
  * @throws {@link WorkflowUpdateRPCTimeoutOrCancelledError} if this Update call timed out or was cancelled. This doesn't
158
158
  * mean the update itself was timed out or cancelled.
@@ -301,7 +301,7 @@ function defaultWorkflowClientOptions(): WithDefaults<WorkflowClientOptions> {
301
301
  };
302
302
  }
303
303
 
304
- function assertRequiredWorkflowOptions(opts: WorkflowOptions): void {
304
+ function assertRequiredWorkflowOptions(opts: WorkflowOptions): asserts opts is WorkflowOptions {
305
305
  if (!opts.taskQueue) {
306
306
  throw new TypeError('Missing WorkflowOptions.taskQueue');
307
307
  }
@@ -450,6 +450,37 @@ export interface IntoHistoriesOptions {
450
450
  bufferLimit?: number;
451
451
  }
452
452
 
453
+ const withStartWorkflowOperationResolve: unique symbol = Symbol();
454
+ const withStartWorkflowOperationReject: unique symbol = Symbol();
455
+ const withStartWorkflowOperationUsed: unique symbol = Symbol();
456
+
457
+ /**
458
+ * Define how to start a workflow when using {@link WorkflowClient.startUpdateWithStart} and
459
+ * {@link WorkflowClient.executeUpdateWithStart}. `workflowIdConflictPolicy` is required in the options.
460
+ *
461
+ * @experimental Update-with-Start is an experimental feature and may be subject to change.
462
+ */
463
+ export class WithStartWorkflowOperation<T extends Workflow> {
464
+ private [withStartWorkflowOperationUsed]: boolean = false;
465
+ private [withStartWorkflowOperationResolve]: ((handle: WorkflowHandle<T>) => void) | undefined = undefined;
466
+ private [withStartWorkflowOperationReject]: ((error: any) => void) | undefined = undefined;
467
+ private workflowHandlePromise: Promise<WorkflowHandle<T>>;
468
+
469
+ constructor(
470
+ public workflowTypeOrFunc: string | T,
471
+ public options: WorkflowStartOptions<T> & { workflowIdConflictPolicy: WorkflowIdConflictPolicy }
472
+ ) {
473
+ this.workflowHandlePromise = new Promise<WorkflowHandle<T>>((resolve, reject) => {
474
+ this[withStartWorkflowOperationResolve] = resolve;
475
+ this[withStartWorkflowOperationReject] = reject;
476
+ });
477
+ }
478
+
479
+ public async workflowHandle(): Promise<WorkflowHandle<T>> {
480
+ return await this.workflowHandlePromise;
481
+ }
482
+ }
483
+
453
484
  /**
454
485
  * Client for starting Workflow executions and creating Workflow handles.
455
486
  *
@@ -478,11 +509,6 @@ export class WorkflowClient extends BaseClient {
478
509
  return this.connection.workflowService;
479
510
  }
480
511
 
481
- /**
482
- * Start a new Workflow execution.
483
- *
484
- * @returns the execution's `runId`.
485
- */
486
512
  protected async _start<T extends Workflow>(
487
513
  workflowTypeOrFunc: string | T,
488
514
  options: WithWorkflowArgs<T, WorkflowOptions>,
@@ -501,12 +527,6 @@ export class WorkflowClient extends BaseClient {
501
527
  });
502
528
  }
503
529
 
504
- /**
505
- * Sends a signal to a running Workflow or starts a new one if not already running and immediately signals it.
506
- * Useful when you're unsure of the Workflow's run state.
507
- *
508
- * @returns the runId of the Workflow
509
- */
510
530
  protected async _signalWithStart<T extends Workflow, SA extends any[]>(
511
531
  workflowTypeOrFunc: string | T,
512
532
  options: WithWorkflowArgs<T, WorkflowSignalWithStartOptions<SA>>,
@@ -535,7 +555,7 @@ export class WorkflowClient extends BaseClient {
535
555
  /**
536
556
  * Start a new Workflow execution.
537
557
  *
538
- * @returns a WorkflowHandle to the started Workflow
558
+ * @returns a {@link WorkflowHandle} to the started Workflow
539
559
  */
540
560
  public async start<T extends Workflow>(
541
561
  workflowTypeOrFunc: string | T,
@@ -559,8 +579,13 @@ export class WorkflowClient extends BaseClient {
559
579
  }
560
580
 
561
581
  /**
562
- * Sends a Signal to a running Workflow or starts a new one if not already running and immediately Signals it.
563
- * Useful when you're unsure whether the Workflow has been started.
582
+ * Start a new Workflow Execution and immediately send a Signal to that Workflow.
583
+ *
584
+ * The behavior of Signal-with-Start in the case where there is already a running Workflow with
585
+ * the given Workflow ID depends on the {@link WorkflowIDConflictPolicy}. That is, if the policy
586
+ * is `USE_EXISTING`, then the Signal is issued against the already existing Workflow Execution;
587
+ * however, if the policy is `FAIL`, then an error is thrown. If no policy is specified,
588
+ * Signal-with-Start defaults to `USE_EXISTING`.
564
589
  *
565
590
  * @returns a {@link WorkflowHandle} to the started Workflow
566
591
  */
@@ -586,7 +611,141 @@ export class WorkflowClient extends BaseClient {
586
611
  }
587
612
 
588
613
  /**
589
- * Starts a new Workflow execution and awaits its completion.
614
+ * Start a new Workflow Execution and immediately send an Update to that Workflow,
615
+ * then await and return the Update's result.
616
+ *
617
+ * The `updateOptions` object must contain a {@link WithStartWorkflowOperation}, which defines
618
+ * the options for the Workflow execution to start (e.g. the Workflow's type, task queue, input
619
+ * arguments, etc.)
620
+ *
621
+ * The behavior of Update-with-Start in the case where there is already a running Workflow with
622
+ * the given Workflow ID depends on the specified {@link WorkflowIDConflictPolicy}. That is, if
623
+ * the policy is `USE_EXISTING`, then the Update is issued against the already existing Workflow
624
+ * Execution; however, if the policy is `FAIL`, then an error is thrown. Caller MUST specify
625
+ * the desired WorkflowIDConflictPolicy.
626
+ *
627
+ * This call will block until the Update has completed. The Workflow handle can be retrieved by
628
+ * awaiting on {@link WithStartWorkflowOperation.workflowHandle}, whether or not the Update
629
+ * succeeds.
630
+ *
631
+ * @returns the Update result
632
+ *
633
+ * @experimental Update-with-Start is an experimental feature and may be subject to change.
634
+ */
635
+ public async executeUpdateWithStart<T extends Workflow, Ret, Args extends any[]>(
636
+ updateDef: UpdateDefinition<Ret, Args> | string,
637
+ updateOptions: WorkflowUpdateOptions & { args?: Args; startWorkflowOperation: WithStartWorkflowOperation<T> }
638
+ ): Promise<Ret> {
639
+ const handle = await this._startUpdateWithStart(updateDef, {
640
+ ...updateOptions,
641
+ waitForStage: WorkflowUpdateStage.COMPLETED,
642
+ });
643
+ return await handle.result();
644
+ }
645
+
646
+ /**
647
+ * Start a new Workflow Execution and immediately send an Update to that Workflow,
648
+ * then return a {@link WorkflowUpdateHandle} for that Update.
649
+ *
650
+ * The `updateOptions` object must contain a {@link WithStartWorkflowOperation}, which defines
651
+ * the options for the Workflow execution to start (e.g. the Workflow's type, task queue, input
652
+ * arguments, etc.)
653
+ *
654
+ * The behavior of Update-with-Start in the case where there is already a running Workflow with
655
+ * the given Workflow ID depends on the specified {@link WorkflowIDConflictPolicy}. That is, if
656
+ * the policy is `USE_EXISTING`, then the Update is issued against the already existing Workflow
657
+ * Execution; however, if the policy is `FAIL`, then an error is thrown. Caller MUST specify
658
+ * the desired WorkflowIDConflictPolicy.
659
+ *
660
+ * This call will block until the Update has reached the specified {@link WorkflowUpdateStage}.
661
+ * Note that this means that the call will not return successfully until the Update has
662
+ * been delivered to a Worker. The Workflow handle can be retrieved by awaiting on
663
+ * {@link WithStartWorkflowOperation.workflowHandle}, whether or not the Update succeeds.
664
+ *
665
+ * @returns a {@link WorkflowUpdateHandle} to the started Update
666
+ *
667
+ * @experimental Update-with-Start is an experimental feature and may be subject to change.
668
+ */
669
+ public async startUpdateWithStart<T extends Workflow, Ret, Args extends any[]>(
670
+ updateDef: UpdateDefinition<Ret, Args> | string,
671
+ updateOptions: WorkflowUpdateOptions & {
672
+ args?: Args;
673
+ waitForStage: 'ACCEPTED';
674
+ startWorkflowOperation: WithStartWorkflowOperation<T>;
675
+ }
676
+ ): Promise<WorkflowUpdateHandle<Ret>> {
677
+ return this._startUpdateWithStart(updateDef, updateOptions);
678
+ }
679
+
680
+ protected async _startUpdateWithStart<T extends Workflow, Ret, Args extends any[]>(
681
+ updateDef: UpdateDefinition<Ret, Args> | string,
682
+ updateWithStartOptions: WorkflowUpdateOptions & {
683
+ args?: Args;
684
+ waitForStage: WorkflowUpdateStage;
685
+ startWorkflowOperation: WithStartWorkflowOperation<T>;
686
+ }
687
+ ): Promise<WorkflowUpdateHandle<Ret>> {
688
+ const { waitForStage, args, startWorkflowOperation, ...updateOptions } = updateWithStartOptions;
689
+ const { workflowTypeOrFunc, options: workflowOptions } = startWorkflowOperation;
690
+ const { workflowId } = workflowOptions;
691
+
692
+ if (startWorkflowOperation[withStartWorkflowOperationUsed]) {
693
+ throw new Error('This WithStartWorkflowOperation instance has already been executed.');
694
+ }
695
+ startWorkflowOperation[withStartWorkflowOperationUsed] = true;
696
+ assertRequiredWorkflowOptions(workflowOptions);
697
+
698
+ const startUpdateWithStartInput: WorkflowStartUpdateWithStartInput = {
699
+ workflowType: extractWorkflowType(workflowTypeOrFunc),
700
+ workflowStartOptions: compileWorkflowOptions(ensureArgs(workflowOptions)),
701
+ workflowStartHeaders: {},
702
+ updateName: typeof updateDef === 'string' ? updateDef : updateDef.name,
703
+ updateArgs: args ?? [],
704
+ updateOptions,
705
+ updateHeaders: {},
706
+ };
707
+
708
+ const interceptors = this.getOrMakeInterceptors(workflowId);
709
+
710
+ const onStart = (startResponse: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse) =>
711
+ startWorkflowOperation[withStartWorkflowOperationResolve]!(
712
+ this._createWorkflowHandle({
713
+ workflowId,
714
+ firstExecutionRunId: startResponse.runId ?? undefined,
715
+ interceptors,
716
+ followRuns: workflowOptions.followRuns ?? true,
717
+ })
718
+ );
719
+
720
+ const onStartError = (err: any) => {
721
+ startWorkflowOperation[withStartWorkflowOperationReject]!(err);
722
+ };
723
+
724
+ const fn = composeInterceptors(
725
+ interceptors,
726
+ 'startUpdateWithStart',
727
+ this._updateWithStartHandler.bind(this, waitForStage, onStart, onStartError)
728
+ );
729
+ const updateOutput = await fn(startUpdateWithStartInput);
730
+
731
+ let outcome = updateOutput.updateOutcome;
732
+ if (!outcome && waitForStage === WorkflowUpdateStage.COMPLETED) {
733
+ outcome = await this._pollForUpdateOutcome(updateOutput.updateId, {
734
+ workflowId,
735
+ runId: updateOutput.workflowExecution.runId,
736
+ });
737
+ }
738
+
739
+ return this.createWorkflowUpdateHandle<Ret>(
740
+ updateOutput.updateId,
741
+ workflowId,
742
+ updateOutput.workflowExecution.runId,
743
+ outcome
744
+ );
745
+ }
746
+
747
+ /**
748
+ * Start a new Workflow execution, then await for its completion and return that Workflow's result.
590
749
  *
591
750
  * @returns the result of the Workflow execution
592
751
  */
@@ -604,9 +763,9 @@ export class WorkflowClient extends BaseClient {
604
763
  }
605
764
 
606
765
  /**
607
- * Gets the result of a Workflow execution.
766
+ * Get the result of a Workflow execution.
608
767
  *
609
- * Follows the chain of execution in case Workflow continues as new, or has a cron schedule or retry policy.
768
+ * Follow the chain of execution in case Workflow continues as new, or has a cron schedule or retry policy.
610
769
  */
611
770
  public async result<T extends Workflow>(
612
771
  workflowId: string,
@@ -755,7 +914,7 @@ export class WorkflowClient extends BaseClient {
755
914
  }
756
915
 
757
916
  /**
758
- * Uses given input to make a queryWorkflow call to the service
917
+ * Use given input to make a queryWorkflow call to the service
759
918
  *
760
919
  * Used as the final function of the query interceptor chain
761
920
  */
@@ -795,31 +954,17 @@ export class WorkflowClient extends BaseClient {
795
954
  return await decodeFromPayloadsAtIndex(this.dataConverter, 0, response.queryResult?.payloads);
796
955
  }
797
956
 
798
- /**
799
- * Start the Update.
800
- *
801
- * Used as the final function of the interceptor chain during startUpdate and executeUpdate.
802
- */
803
- protected async _startUpdateHandler(
804
- waitForStage: WorkflowUpdateStage,
957
+ protected async _createUpdateWorkflowRequest(
958
+ lifecycleStage: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage,
805
959
  input: WorkflowStartUpdateInput
806
- ): Promise<WorkflowStartUpdateOutput> {
807
- let waitForStageProto: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage =
808
- encodeWorkflowUpdateStage(waitForStage) ??
809
- UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED;
810
-
811
- waitForStageProto =
812
- waitForStageProto >= UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED
813
- ? waitForStageProto
814
- : UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED;
815
-
960
+ ): Promise<temporal.api.workflowservice.v1.IUpdateWorkflowExecutionRequest> {
816
961
  const updateId = input.options?.updateId ?? uuid4();
817
- const req: temporal.api.workflowservice.v1.IUpdateWorkflowExecutionRequest = {
962
+ return {
818
963
  namespace: this.options.namespace,
819
964
  workflowExecution: input.workflowExecution,
820
965
  firstExecutionRunId: input.firstExecutionRunId,
821
966
  waitPolicy: {
822
- lifecycleStage: waitForStageProto,
967
+ lifecycleStage,
823
968
  },
824
969
  request: {
825
970
  meta: {
@@ -833,26 +978,140 @@ export class WorkflowClient extends BaseClient {
833
978
  },
834
979
  },
835
980
  };
981
+ }
836
982
 
837
- // Repeatedly send UpdateWorkflowExecution until update is >= Accepted or >= `waitForStage` (if
838
- // the server receives a request with an update ID that already exists, it responds with
839
- // information for the existing update).
983
+ /**
984
+ * Start the Update.
985
+ *
986
+ * Used as the final function of the interceptor chain during startUpdate and executeUpdate.
987
+ */
988
+ protected async _startUpdateHandler(
989
+ waitForStage: WorkflowUpdateStage,
990
+ input: WorkflowStartUpdateInput
991
+ ): Promise<WorkflowStartUpdateOutput> {
992
+ let waitForStageProto: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage =
993
+ encodeWorkflowUpdateStage(waitForStage) ??
994
+ UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED;
995
+
996
+ waitForStageProto =
997
+ waitForStageProto >= UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED
998
+ ? waitForStageProto
999
+ : UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED;
1000
+
1001
+ const request = await this._createUpdateWorkflowRequest(waitForStageProto, input);
1002
+
1003
+ // Repeatedly send UpdateWorkflowExecution until update is durable (if the server receives a request with
1004
+ // an update ID that already exists, it responds with information for the existing update). If the
1005
+ // requested wait stage is COMPLETED, further polling is done before returning the UpdateHandle.
840
1006
  let response: temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse;
841
1007
  try {
842
1008
  do {
843
- response = await this.workflowService.updateWorkflowExecution(req);
844
- } while (response.stage < waitForStageProto);
1009
+ response = await this.workflowService.updateWorkflowExecution(request);
1010
+ } while (
1011
+ response.stage < UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED
1012
+ );
845
1013
  } catch (err) {
846
1014
  this.rethrowUpdateGrpcError(err, 'Workflow Update failed', input.workflowExecution);
847
1015
  }
848
1016
  return {
849
- updateId,
1017
+ updateId: request.request!.meta!.updateId!,
850
1018
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
851
1019
  workflowRunId: response.updateRef!.workflowExecution!.runId!,
852
1020
  outcome: response.outcome ?? undefined,
853
1021
  };
854
1022
  }
855
1023
 
1024
+ /**
1025
+ * Send the Update-With-Start MultiOperation request.
1026
+ *
1027
+ * Used as the final function of the interceptor chain during
1028
+ * startUpdateWithStart and executeUpdateWithStart.
1029
+ */
1030
+ protected async _updateWithStartHandler(
1031
+ waitForStage: WorkflowUpdateStage,
1032
+ onStart: (startResponse: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse) => void,
1033
+ onStartError: (err: any) => void,
1034
+ input: WorkflowStartUpdateWithStartInput
1035
+ ): Promise<WorkflowStartUpdateWithStartOutput> {
1036
+ const startInput: WorkflowStartInput = {
1037
+ workflowType: input.workflowType,
1038
+ options: input.workflowStartOptions,
1039
+ headers: input.workflowStartHeaders,
1040
+ };
1041
+ const updateInput: WorkflowStartUpdateInput = {
1042
+ updateName: input.updateName,
1043
+ args: input.updateArgs,
1044
+ workflowExecution: {
1045
+ workflowId: input.workflowStartOptions.workflowId,
1046
+ },
1047
+ options: input.updateOptions,
1048
+ headers: input.updateHeaders,
1049
+ };
1050
+ let seenStart = false;
1051
+ try {
1052
+ const startRequest = await this.createStartWorkflowRequest(startInput);
1053
+ const waitForStageProto = encodeWorkflowUpdateStage(waitForStage)!;
1054
+ const updateRequest = await this._createUpdateWorkflowRequest(waitForStageProto, updateInput);
1055
+ const multiOpReq: temporal.api.workflowservice.v1.IExecuteMultiOperationRequest = {
1056
+ namespace: this.options.namespace,
1057
+ operations: [
1058
+ {
1059
+ startWorkflow: startRequest,
1060
+ },
1061
+ {
1062
+ updateWorkflow: updateRequest,
1063
+ },
1064
+ ],
1065
+ };
1066
+
1067
+ let multiOpResp: temporal.api.workflowservice.v1.IExecuteMultiOperationResponse;
1068
+ let startResp: temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse;
1069
+ let updateResp: temporal.api.workflowservice.v1.IUpdateWorkflowExecutionResponse;
1070
+ let reachedStage: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage;
1071
+ // Repeatedly send ExecuteMultiOperation until update is durable (if the server receives a request with
1072
+ // an update ID that already exists, it responds with information for the existing update). If the
1073
+ // requested wait stage is COMPLETED, further polling is done before returning the UpdateHandle.
1074
+ do {
1075
+ multiOpResp = await this.workflowService.executeMultiOperation(multiOpReq);
1076
+ startResp = multiOpResp.responses?.[0]
1077
+ ?.startWorkflow as temporal.api.workflowservice.v1.IStartWorkflowExecutionResponse;
1078
+ if (!seenStart) {
1079
+ onStart(startResp);
1080
+ seenStart = true;
1081
+ }
1082
+ updateResp = multiOpResp.responses?.[1]
1083
+ ?.updateWorkflow as temporal.api.workflowservice.v1.IUpdateWorkflowExecutionResponse;
1084
+ reachedStage =
1085
+ updateResp.stage ??
1086
+ UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_UNSPECIFIED;
1087
+ } while (reachedStage < UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED);
1088
+ return {
1089
+ workflowExecution: {
1090
+ workflowId: updateResp.updateRef!.workflowExecution!.workflowId!,
1091
+ runId: updateResp.updateRef!.workflowExecution!.runId!,
1092
+ },
1093
+ updateId: updateRequest.request!.meta!.updateId!,
1094
+ updateOutcome: updateResp.outcome ?? undefined,
1095
+ };
1096
+ } catch (thrownError) {
1097
+ let err = thrownError;
1098
+ if (isGrpcServiceError(err) && err.code === grpcStatus.ALREADY_EXISTS) {
1099
+ err = new WorkflowExecutionAlreadyStartedError(
1100
+ 'Workflow execution already started',
1101
+ input.workflowStartOptions.workflowId,
1102
+ input.workflowType
1103
+ );
1104
+ }
1105
+ if (!seenStart) {
1106
+ onStartError(err);
1107
+ }
1108
+ if (isGrpcServiceError(err)) {
1109
+ this.rethrowUpdateGrpcError(err, 'Update-With-Start failed', updateInput.workflowExecution);
1110
+ }
1111
+ throw err;
1112
+ }
1113
+ }
1114
+
856
1115
  protected createWorkflowUpdateHandle<Ret>(
857
1116
  updateId: string,
858
1117
  workflowId: string,
@@ -908,7 +1167,7 @@ export class WorkflowClient extends BaseClient {
908
1167
  }
909
1168
 
910
1169
  /**
911
- * Uses given input to make a signalWorkflowExecution call to the service
1170
+ * Use given input to make a signalWorkflowExecution call to the service
912
1171
  *
913
1172
  * Used as the final function of the signal interceptor chain
914
1173
  */
@@ -931,7 +1190,7 @@ export class WorkflowClient extends BaseClient {
931
1190
  }
932
1191
 
933
1192
  /**
934
- * Uses given input to make a signalWithStartWorkflowExecution call to the service
1193
+ * Use given input to make a signalWithStartWorkflowExecution call to the service
935
1194
  *
936
1195
  * Used as the final function of the signalWithStart interceptor chain
937
1196
  */
@@ -982,15 +1241,32 @@ export class WorkflowClient extends BaseClient {
982
1241
  }
983
1242
 
984
1243
  /**
985
- * Uses given input to make startWorkflowExecution call to the service
1244
+ * Use given input to make startWorkflowExecution call to the service
986
1245
  *
987
1246
  * Used as the final function of the start interceptor chain
988
1247
  */
989
1248
  protected async _startWorkflowHandler(input: WorkflowStartInput): Promise<string> {
1249
+ const req = await this.createStartWorkflowRequest(input);
1250
+ const { options: opts, workflowType } = input;
1251
+ try {
1252
+ return (await this.workflowService.startWorkflowExecution(req)).runId;
1253
+ } catch (err: any) {
1254
+ if (err.code === grpcStatus.ALREADY_EXISTS) {
1255
+ throw new WorkflowExecutionAlreadyStartedError(
1256
+ 'Workflow execution already started',
1257
+ opts.workflowId,
1258
+ workflowType
1259
+ );
1260
+ }
1261
+ this.rethrowGrpcError(err, 'Failed to start Workflow', { workflowId: opts.workflowId });
1262
+ }
1263
+ }
1264
+
1265
+ protected async createStartWorkflowRequest(input: WorkflowStartInput): Promise<StartWorkflowExecutionRequest> {
990
1266
  const { options: opts, workflowType, headers } = input;
991
- const { identity } = this.options;
992
- const req: StartWorkflowExecutionRequest = {
993
- namespace: this.options.namespace,
1267
+ const { identity, namespace } = this.options;
1268
+ return {
1269
+ namespace,
994
1270
  identity,
995
1271
  requestId: uuid4(),
996
1272
  workflowId: opts.workflowId,
@@ -1016,22 +1292,10 @@ export class WorkflowClient extends BaseClient {
1016
1292
  cronSchedule: opts.cronSchedule,
1017
1293
  header: { fields: headers },
1018
1294
  };
1019
- try {
1020
- return (await this.workflowService.startWorkflowExecution(req)).runId;
1021
- } catch (err: any) {
1022
- if (err.code === grpcStatus.ALREADY_EXISTS) {
1023
- throw new WorkflowExecutionAlreadyStartedError(
1024
- 'Workflow execution already started',
1025
- opts.workflowId,
1026
- workflowType
1027
- );
1028
- }
1029
- this.rethrowGrpcError(err, 'Failed to start Workflow', { workflowId: opts.workflowId });
1030
- }
1031
1295
  }
1032
1296
 
1033
1297
  /**
1034
- * Uses given input to make terminateWorkflowExecution call to the service
1298
+ * Use given input to make terminateWorkflowExecution call to the service
1035
1299
  *
1036
1300
  * Used as the final function of the terminate interceptor chain
1037
1301
  */
@@ -1285,9 +1549,9 @@ export class WorkflowClient extends BaseClient {
1285
1549
  }
1286
1550
 
1287
1551
  /**
1288
- * List workflows by given `query`.
1552
+ * Return a list of Workflow Executions matching the given `query`.
1289
1553
  *
1290
- * ⚠️ To use advanced query functionality, as of the 1.18 server release, you must use Elasticsearch based visibility.
1554
+ * Note that the list of Workflow Executions returned is approximate and eventually consistent.
1291
1555
  *
1292
1556
  * More info on the concept of "visibility" and the query syntax on the Temporal documentation site:
1293
1557
  * https://docs.temporal.io/visibility
@@ -1308,6 +1572,29 @@ export class WorkflowClient extends BaseClient {
1308
1572
  };
1309
1573
  }
1310
1574
 
1575
+ /**
1576
+ * Return the number of Workflow Executions matching the given `query`. If no `query` is provided, then return the
1577
+ * total number of Workflow Executions for this namespace.
1578
+ *
1579
+ * Note that the number of Workflow Executions returned is approximate and eventually consistent.
1580
+ *
1581
+ * More info on the concept of "visibility" and the query syntax on the Temporal documentation site:
1582
+ * https://docs.temporal.io/visibility
1583
+ */
1584
+ public async count(query?: string): Promise<CountWorkflowExecution> {
1585
+ let response: temporal.api.workflowservice.v1.CountWorkflowExecutionsResponse;
1586
+ try {
1587
+ response = await this.workflowService.countWorkflowExecutions({
1588
+ namespace: this.options.namespace,
1589
+ query,
1590
+ });
1591
+ } catch (e) {
1592
+ this.rethrowGrpcError(e, 'Failed to count workflows');
1593
+ }
1594
+
1595
+ return decodeCountWorkflowExecutionsResponse(response);
1596
+ }
1597
+
1311
1598
  protected getOrMakeInterceptors(workflowId: string, runId?: string): WorkflowClientInterceptor[] {
1312
1599
  if (typeof this.options.interceptors === 'object' && 'calls' in this.options.interceptors) {
1313
1600
  // eslint-disable-next-line deprecation/deprecation