@temporalio/client 0.22.0 → 1.0.0-rc.1
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.
- package/README.md +2 -2
- package/lib/async-completion-client.d.ts +12 -7
- package/lib/async-completion-client.js +16 -13
- package/lib/async-completion-client.js.map +1 -1
- package/lib/connection.d.ts +97 -47
- package/lib/connection.js +140 -25
- package/lib/connection.js.map +1 -1
- package/lib/index.d.ts +5 -3
- package/lib/index.js +8 -4
- package/lib/index.js.map +1 -1
- package/lib/pkg.d.ts +5 -0
- package/lib/pkg.js +12 -0
- package/lib/pkg.js.map +1 -0
- package/lib/types.d.ts +50 -2
- package/lib/types.js +4 -0
- package/lib/types.js.map +1 -1
- package/lib/workflow-client.d.ts +55 -15
- package/lib/workflow-client.js +73 -20
- package/lib/workflow-client.js.map +1 -1
- package/lib/workflow-options.d.ts +2 -2
- package/package.json +12 -11
- package/src/async-completion-client.ts +256 -0
- package/src/connection.ts +374 -0
- package/src/errors.ts +57 -0
- package/src/grpc-retry.ts +119 -0
- package/src/index.ts +37 -0
- package/src/interceptors.ts +132 -0
- package/src/pkg.ts +7 -0
- package/src/types.ts +88 -0
- package/src/workflow-client.ts +969 -0
- package/src/workflow-options.ts +76 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
import { status as grpcStatus } from '@grpc/grpc-js';
|
|
2
|
+
import {
|
|
3
|
+
CancelledFailure,
|
|
4
|
+
DataConverter,
|
|
5
|
+
LoadedDataConverter,
|
|
6
|
+
mapFromPayloads,
|
|
7
|
+
mapToPayloads,
|
|
8
|
+
RetryState,
|
|
9
|
+
searchAttributePayloadConverter,
|
|
10
|
+
TerminatedFailure,
|
|
11
|
+
TimeoutFailure,
|
|
12
|
+
TimeoutType,
|
|
13
|
+
} from '@temporalio/common';
|
|
14
|
+
import {
|
|
15
|
+
decodeArrayFromPayloads,
|
|
16
|
+
decodeFromPayloadsAtIndex,
|
|
17
|
+
decodeMapFromPayloads,
|
|
18
|
+
decodeOptionalFailureToOptionalError,
|
|
19
|
+
encodeMapToPayloads,
|
|
20
|
+
encodeToPayloads,
|
|
21
|
+
loadDataConverter,
|
|
22
|
+
} from '@temporalio/internal-non-workflow-common';
|
|
23
|
+
import {
|
|
24
|
+
BaseWorkflowHandle,
|
|
25
|
+
compileRetryPolicy,
|
|
26
|
+
composeInterceptors,
|
|
27
|
+
optionalTsToDate,
|
|
28
|
+
QueryDefinition,
|
|
29
|
+
Replace,
|
|
30
|
+
SearchAttributes,
|
|
31
|
+
SignalDefinition,
|
|
32
|
+
tsToDate,
|
|
33
|
+
WithWorkflowArgs,
|
|
34
|
+
Workflow,
|
|
35
|
+
WorkflowNotFoundError,
|
|
36
|
+
WorkflowResultType,
|
|
37
|
+
} from '@temporalio/internal-workflow-common';
|
|
38
|
+
import { temporal } from '@temporalio/proto';
|
|
39
|
+
import os from 'os';
|
|
40
|
+
import { v4 as uuid4 } from 'uuid';
|
|
41
|
+
import { Connection } from './connection';
|
|
42
|
+
import {
|
|
43
|
+
isServerErrorResponse,
|
|
44
|
+
ServiceError,
|
|
45
|
+
WorkflowContinuedAsNewError,
|
|
46
|
+
WorkflowExecutionAlreadyStartedError,
|
|
47
|
+
WorkflowFailedError,
|
|
48
|
+
} from './errors';
|
|
49
|
+
import {
|
|
50
|
+
WorkflowCancelInput,
|
|
51
|
+
WorkflowClientCallsInterceptor,
|
|
52
|
+
WorkflowClientInterceptors,
|
|
53
|
+
WorkflowDescribeInput,
|
|
54
|
+
WorkflowQueryInput,
|
|
55
|
+
WorkflowSignalInput,
|
|
56
|
+
WorkflowSignalWithStartInput,
|
|
57
|
+
WorkflowStartInput,
|
|
58
|
+
WorkflowTerminateInput,
|
|
59
|
+
} from './interceptors';
|
|
60
|
+
import {
|
|
61
|
+
ConnectionLike,
|
|
62
|
+
DescribeWorkflowExecutionResponse,
|
|
63
|
+
GetWorkflowExecutionHistoryRequest,
|
|
64
|
+
Metadata,
|
|
65
|
+
RequestCancelWorkflowExecutionResponse,
|
|
66
|
+
StartWorkflowExecutionRequest,
|
|
67
|
+
TerminateWorkflowExecutionResponse,
|
|
68
|
+
WorkflowExecution,
|
|
69
|
+
WorkflowExecutionDescription,
|
|
70
|
+
WorkflowExecutionStatusName,
|
|
71
|
+
WorkflowService,
|
|
72
|
+
} from './types';
|
|
73
|
+
import { compileWorkflowOptions, WorkflowOptions, WorkflowSignalWithStartOptions } from './workflow-options';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A client side handle to a single Workflow instance.
|
|
77
|
+
* It can be used to start, signal, query, wait for completion, terminate and cancel a Workflow execution.
|
|
78
|
+
*
|
|
79
|
+
* Given the following Workflow definition:
|
|
80
|
+
* ```ts
|
|
81
|
+
* export const incrementSignal = defineSignal('increment');
|
|
82
|
+
* export const getValueQuery = defineQuery<number>('getValue');
|
|
83
|
+
* export async function counterWorkflow(initialValue: number): Promise<void>;
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Create a handle for running and interacting with a single Workflow:
|
|
87
|
+
* ```ts
|
|
88
|
+
* const client = new WorkflowClient();
|
|
89
|
+
* // Start the Workflow with initialValue of 2.
|
|
90
|
+
* const handle = await client.start({
|
|
91
|
+
* workflowType: counterWorkflow,
|
|
92
|
+
* args: [2],
|
|
93
|
+
* taskQueue: 'tutorial',
|
|
94
|
+
* });
|
|
95
|
+
* await handle.signal(incrementSignal, 2);
|
|
96
|
+
* await handle.query(getValueQuery); // 4
|
|
97
|
+
* await handle.cancel();
|
|
98
|
+
* await handle.result(); // throws WorkflowExecutionCancelledError
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export interface WorkflowHandle<T extends Workflow = Workflow> extends BaseWorkflowHandle<T> {
|
|
102
|
+
/**
|
|
103
|
+
* Query a running or completed Workflow.
|
|
104
|
+
*
|
|
105
|
+
* @param def a query definition as returned from {@link defineQuery} or query name (string)
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* await handle.query(getValueQuery);
|
|
110
|
+
* await handle.query<number, []>('getValue');
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
query<Ret, Args extends any[] = []>(def: QueryDefinition<Ret, Args> | string, ...args: Args): Promise<Ret>;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Terminate a running Workflow
|
|
117
|
+
*/
|
|
118
|
+
terminate(reason?: string): Promise<TerminateWorkflowExecutionResponse>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cancel a running Workflow
|
|
122
|
+
*/
|
|
123
|
+
cancel(): Promise<RequestCancelWorkflowExecutionResponse>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Describe the current workflow execution
|
|
127
|
+
*/
|
|
128
|
+
describe(): Promise<WorkflowExecutionDescription>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Readonly accessor to the underlying WorkflowClient
|
|
132
|
+
*/
|
|
133
|
+
readonly client: WorkflowClient;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* This interface is exactly the same as {@link WorkflowHandle} except it
|
|
138
|
+
* includes the `firstExecutionRunId` returned from {@link WorkflowClient.start}.
|
|
139
|
+
*/
|
|
140
|
+
export interface WorkflowHandleWithFirstExecutionRunId<T extends Workflow = Workflow> extends WorkflowHandle<T> {
|
|
141
|
+
/**
|
|
142
|
+
* Run Id of the first Execution in the Workflow Execution Chain.
|
|
143
|
+
*/
|
|
144
|
+
readonly firstExecutionRunId: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* This interface is exactly the same as {@link WorkflowHandle} except it
|
|
149
|
+
* includes the `signaledRunId` returned from `signalWithStart`.
|
|
150
|
+
*/
|
|
151
|
+
export interface WorkflowHandleWithSignaledRunId<T extends Workflow = Workflow> extends WorkflowHandle<T> {
|
|
152
|
+
/**
|
|
153
|
+
* The Run Id of the bound Workflow at the time of {@link WorkflowClient.signalWithStart}.
|
|
154
|
+
*
|
|
155
|
+
* Since `signalWithStart` may have signaled an existing Workflow Chain, `signaledRunId` might not be the
|
|
156
|
+
* `firstExecutionRunId`.
|
|
157
|
+
*/
|
|
158
|
+
readonly signaledRunId: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface WorkflowClientOptions {
|
|
162
|
+
/**
|
|
163
|
+
* {@link DataConverter} to use for serializing and deserializing payloads
|
|
164
|
+
*/
|
|
165
|
+
dataConverter?: DataConverter;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Used to override and extend default Connection functionality
|
|
169
|
+
*
|
|
170
|
+
* Useful for injecting auth headers and tracing Workflow executions
|
|
171
|
+
*/
|
|
172
|
+
interceptors?: WorkflowClientInterceptors;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Identity to report to the server
|
|
176
|
+
*
|
|
177
|
+
* @default `${process.pid}@${os.hostname()}`
|
|
178
|
+
*/
|
|
179
|
+
identity?: string;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Connection to use to communicate with the server.
|
|
183
|
+
*
|
|
184
|
+
* By default `WorkflowClient` connects to localhost.
|
|
185
|
+
*
|
|
186
|
+
* Connections are expensive to construct and should be reused.
|
|
187
|
+
*/
|
|
188
|
+
connection?: ConnectionLike;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Server namespace
|
|
192
|
+
*
|
|
193
|
+
* @default default
|
|
194
|
+
*/
|
|
195
|
+
namespace?: string;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Should a query be rejected by closed and failed workflows
|
|
199
|
+
*
|
|
200
|
+
* @default QUERY_REJECT_CONDITION_UNSPECIFIED which means that closed and failed workflows are still queryable
|
|
201
|
+
*/
|
|
202
|
+
queryRejectCondition?: temporal.api.enums.v1.QueryRejectCondition;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type WorkflowClientOptionsWithDefaults = Replace<
|
|
206
|
+
Required<WorkflowClientOptions>,
|
|
207
|
+
{
|
|
208
|
+
connection?: ConnectionLike;
|
|
209
|
+
}
|
|
210
|
+
>;
|
|
211
|
+
export type LoadedWorkflowClientOptions = WorkflowClientOptionsWithDefaults & {
|
|
212
|
+
loadedDataConverter: LoadedDataConverter;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export function defaultWorkflowClientOptions(): WorkflowClientOptionsWithDefaults {
|
|
216
|
+
return {
|
|
217
|
+
dataConverter: {},
|
|
218
|
+
// The equivalent in Java is ManagementFactory.getRuntimeMXBean().getName()
|
|
219
|
+
identity: `${process.pid}@${os.hostname()}`,
|
|
220
|
+
interceptors: {},
|
|
221
|
+
namespace: 'default',
|
|
222
|
+
queryRejectCondition: temporal.api.enums.v1.QueryRejectCondition.QUERY_REJECT_CONDITION_UNSPECIFIED,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function assertRequiredWorkflowOptions(opts: WorkflowOptions): void {
|
|
227
|
+
if (!opts.taskQueue) {
|
|
228
|
+
throw new TypeError('Missing WorkflowOptions.taskQueue');
|
|
229
|
+
}
|
|
230
|
+
if (!opts.workflowId) {
|
|
231
|
+
throw new TypeError('Missing WorkflowOptions.workflowId');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function ensureArgs<W extends Workflow, T extends WorkflowStartOptions<W>>(
|
|
236
|
+
opts: T
|
|
237
|
+
): Omit<T, 'args'> & { args: unknown[] } {
|
|
238
|
+
const { args, ...rest } = opts;
|
|
239
|
+
return { args: args ?? [], ...rest };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Options for getting a result of a Workflow execution.
|
|
244
|
+
*/
|
|
245
|
+
export interface WorkflowResultOptions {
|
|
246
|
+
/**
|
|
247
|
+
* If set to true, instructs the client to follow the chain of execution before returning a Workflow's result.
|
|
248
|
+
*
|
|
249
|
+
* Workflow execution is chained if the Workflow has a cron schedule or continues-as-new or configured to retry
|
|
250
|
+
* after failure or timeout.
|
|
251
|
+
*
|
|
252
|
+
* @default true
|
|
253
|
+
*/
|
|
254
|
+
followRuns?: boolean;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface GetWorkflowHandleOptions extends WorkflowResultOptions {
|
|
258
|
+
/**
|
|
259
|
+
* ID of the first execution in the Workflow execution chain.
|
|
260
|
+
*
|
|
261
|
+
* When getting a handle with no `runId`, pass this option to ensure some
|
|
262
|
+
* {@link WorkflowHandle} methods (e.g. `terminate` and `cancel`) don't
|
|
263
|
+
* affect executions from another chain.
|
|
264
|
+
*/
|
|
265
|
+
firstExecutionRunId?: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
interface WorkflowHandleOptions extends GetWorkflowHandleOptions {
|
|
269
|
+
workflowId: string;
|
|
270
|
+
runId?: string;
|
|
271
|
+
interceptors: WorkflowClientCallsInterceptor[];
|
|
272
|
+
/**
|
|
273
|
+
* A runId to use for getting the workflow's result.
|
|
274
|
+
*
|
|
275
|
+
* - When creating a handle using `getHandle`, uses the provided runId or firstExecutionRunId
|
|
276
|
+
* - When creating a handle using `start`, uses the returned runId (first in the chain)
|
|
277
|
+
* - When creating a handle using `signalWithStart`, uses the the returned runId
|
|
278
|
+
*/
|
|
279
|
+
runIdForResult?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Options for starting a Workflow
|
|
284
|
+
*/
|
|
285
|
+
export type WorkflowStartOptions<T extends Workflow = Workflow> = WithWorkflowArgs<T, WorkflowOptions>;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Client for starting Workflow executions and creating Workflow handles
|
|
289
|
+
*/
|
|
290
|
+
export class WorkflowClient {
|
|
291
|
+
public readonly options: LoadedWorkflowClientOptions;
|
|
292
|
+
public readonly connection: ConnectionLike;
|
|
293
|
+
|
|
294
|
+
constructor(options?: WorkflowClientOptions) {
|
|
295
|
+
this.connection = options?.connection ?? Connection.lazy();
|
|
296
|
+
this.options = {
|
|
297
|
+
...defaultWorkflowClientOptions(),
|
|
298
|
+
...options,
|
|
299
|
+
loadedDataConverter: loadDataConverter(options?.dataConverter),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Raw gRPC access to the Temporal service.
|
|
305
|
+
*
|
|
306
|
+
* **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service.
|
|
307
|
+
*/
|
|
308
|
+
get workflowService(): WorkflowService {
|
|
309
|
+
return this.connection.workflowService;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Set the deadline for any service requests executed in `fn`'s scope.
|
|
314
|
+
*/
|
|
315
|
+
async withDeadline<R>(deadline: number | Date, fn: () => Promise<R>): Promise<R> {
|
|
316
|
+
return await this.connection.withDeadline(deadline, fn);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Set metadata for any service requests executed in `fn`'s scope.
|
|
321
|
+
*
|
|
322
|
+
* @returns returned value of `fn`
|
|
323
|
+
*
|
|
324
|
+
* @see {@link Connection.withMetadata}
|
|
325
|
+
*/
|
|
326
|
+
async withMetadata<R>(metadata: Metadata, fn: () => Promise<R>): Promise<R> {
|
|
327
|
+
return await this.connection.withMetadata(metadata, fn);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Start a new Workflow execution.
|
|
332
|
+
*
|
|
333
|
+
* @returns the execution's `runId`.
|
|
334
|
+
*/
|
|
335
|
+
protected async _start<T extends Workflow>(
|
|
336
|
+
workflowTypeOrFunc: string | T,
|
|
337
|
+
options: WithWorkflowArgs<T, WorkflowOptions>,
|
|
338
|
+
interceptors: WorkflowClientCallsInterceptor[]
|
|
339
|
+
): Promise<string> {
|
|
340
|
+
const workflowType = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
|
|
341
|
+
assertRequiredWorkflowOptions(options);
|
|
342
|
+
const compiledOptions = compileWorkflowOptions(ensureArgs(options));
|
|
343
|
+
|
|
344
|
+
const start = composeInterceptors(interceptors, 'start', this._startWorkflowHandler.bind(this));
|
|
345
|
+
|
|
346
|
+
return start({
|
|
347
|
+
options: compiledOptions,
|
|
348
|
+
headers: {},
|
|
349
|
+
workflowType,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Sends a signal to a running Workflow or starts a new one if not already running and immediately signals it.
|
|
355
|
+
* Useful when you're unsure of the Workflows' run state.
|
|
356
|
+
*
|
|
357
|
+
* @returns the runId of the Workflow
|
|
358
|
+
*/
|
|
359
|
+
protected async _signalWithStart<T extends Workflow, SA extends any[]>(
|
|
360
|
+
workflowTypeOrFunc: string | T,
|
|
361
|
+
options: WithWorkflowArgs<T, WorkflowSignalWithStartOptions<SA>>,
|
|
362
|
+
interceptors: WorkflowClientCallsInterceptor[]
|
|
363
|
+
): Promise<string> {
|
|
364
|
+
const workflowType = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
|
|
365
|
+
const { signal, signalArgs, ...rest } = options;
|
|
366
|
+
assertRequiredWorkflowOptions(rest);
|
|
367
|
+
const compiledOptions = compileWorkflowOptions(ensureArgs(rest));
|
|
368
|
+
|
|
369
|
+
const signalWithStart = composeInterceptors(
|
|
370
|
+
interceptors,
|
|
371
|
+
'signalWithStart',
|
|
372
|
+
this._signalWithStartWorkflowHandler.bind(this)
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
return signalWithStart({
|
|
376
|
+
options: compiledOptions,
|
|
377
|
+
headers: {},
|
|
378
|
+
workflowType,
|
|
379
|
+
signalName: typeof signal === 'string' ? signal : signal.name,
|
|
380
|
+
signalArgs,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Start a new Workflow execution.
|
|
386
|
+
*
|
|
387
|
+
* @returns a WorkflowHandle to the started Workflow
|
|
388
|
+
*/
|
|
389
|
+
public async start<T extends Workflow>(
|
|
390
|
+
workflowTypeOrFunc: string | T,
|
|
391
|
+
options: WorkflowStartOptions<T>
|
|
392
|
+
): Promise<WorkflowHandleWithFirstExecutionRunId<T>> {
|
|
393
|
+
const { workflowId } = options;
|
|
394
|
+
// Cast is needed because it's impossible to deduce the type in this situation
|
|
395
|
+
const interceptors = (this.options.interceptors.calls ?? []).map((ctor) => ctor({ workflowId }));
|
|
396
|
+
const runId = await this._start(workflowTypeOrFunc, { ...options, workflowId }, interceptors);
|
|
397
|
+
// runId is not used in handles created with `start*` calls because these
|
|
398
|
+
// handles should allow interacting with the workflow if it continues as new.
|
|
399
|
+
const handle = this._createWorkflowHandle({
|
|
400
|
+
workflowId,
|
|
401
|
+
runId: undefined,
|
|
402
|
+
firstExecutionRunId: runId,
|
|
403
|
+
runIdForResult: runId,
|
|
404
|
+
interceptors,
|
|
405
|
+
followRuns: options.followRuns ?? true,
|
|
406
|
+
}) as WorkflowHandleWithFirstExecutionRunId<T>; // Cast is safe because we know we add the firstExecutionRunId below
|
|
407
|
+
(handle as any) /* readonly */.firstExecutionRunId = runId;
|
|
408
|
+
return handle;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Sends a signal to a running Workflow or starts a new one if not already running and immediately signals it.
|
|
413
|
+
* Useful when you're unsure of the Workflows' run state.
|
|
414
|
+
*
|
|
415
|
+
* @returns a WorkflowHandle to the started Workflow
|
|
416
|
+
*/
|
|
417
|
+
public async signalWithStart<T extends Workflow, SA extends any[] = []>(
|
|
418
|
+
workflowTypeOrFunc: string | T,
|
|
419
|
+
options: WithWorkflowArgs<T, WorkflowSignalWithStartOptions<SA>>
|
|
420
|
+
): Promise<WorkflowHandleWithSignaledRunId<T>> {
|
|
421
|
+
const { workflowId } = options;
|
|
422
|
+
const interceptors = (this.options.interceptors.calls ?? []).map((ctor) => ctor({ workflowId }));
|
|
423
|
+
const runId = await this._signalWithStart(workflowTypeOrFunc, options, interceptors);
|
|
424
|
+
// runId is not used in handles created with `start*` calls because these
|
|
425
|
+
// handles should allow interacting with the workflow if it continues as new.
|
|
426
|
+
const handle = this._createWorkflowHandle({
|
|
427
|
+
workflowId,
|
|
428
|
+
runId: undefined,
|
|
429
|
+
firstExecutionRunId: undefined, // We don't know if this runId is first in the chain or not
|
|
430
|
+
runIdForResult: runId,
|
|
431
|
+
interceptors,
|
|
432
|
+
followRuns: options.followRuns ?? true,
|
|
433
|
+
}) as WorkflowHandleWithSignaledRunId<T>; // Cast is safe because we know we add the signaledRunId below
|
|
434
|
+
(handle as any) /* readonly */.signaledRunId = runId;
|
|
435
|
+
return handle;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Starts a new Workflow execution and awaits its completion.
|
|
440
|
+
*
|
|
441
|
+
* @returns the result of the Workflow execution
|
|
442
|
+
*/
|
|
443
|
+
public async execute<T extends Workflow>(
|
|
444
|
+
workflowTypeOrFunc: string | T,
|
|
445
|
+
options: WorkflowStartOptions<T>
|
|
446
|
+
): Promise<WorkflowResultType<T>> {
|
|
447
|
+
const { workflowId } = options;
|
|
448
|
+
const interceptors = (this.options.interceptors.calls ?? []).map((ctor) => ctor({ workflowId }));
|
|
449
|
+
await this._start(workflowTypeOrFunc, options, interceptors);
|
|
450
|
+
return await this.result(workflowId, undefined, {
|
|
451
|
+
...options,
|
|
452
|
+
followRuns: options.followRuns ?? true,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Gets the result of a Workflow execution.
|
|
458
|
+
*
|
|
459
|
+
* Follows the chain of execution in case Workflow continues as new, or has a cron schedule or retry policy.
|
|
460
|
+
*/
|
|
461
|
+
public async result<T extends Workflow>(
|
|
462
|
+
workflowId: string,
|
|
463
|
+
runId?: string,
|
|
464
|
+
opts?: WorkflowResultOptions
|
|
465
|
+
): Promise<WorkflowResultType<T>> {
|
|
466
|
+
const followRuns = opts?.followRuns ?? true;
|
|
467
|
+
const execution: temporal.api.common.v1.IWorkflowExecution = { workflowId, runId };
|
|
468
|
+
const req: GetWorkflowExecutionHistoryRequest = {
|
|
469
|
+
namespace: this.options.namespace,
|
|
470
|
+
execution,
|
|
471
|
+
skipArchival: true,
|
|
472
|
+
waitNewEvent: true,
|
|
473
|
+
historyEventFilterType: temporal.api.enums.v1.HistoryEventFilterType.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT,
|
|
474
|
+
};
|
|
475
|
+
let ev: temporal.api.history.v1.IHistoryEvent;
|
|
476
|
+
|
|
477
|
+
for (;;) {
|
|
478
|
+
let res: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse;
|
|
479
|
+
try {
|
|
480
|
+
res = await this.workflowService.getWorkflowExecutionHistory(req);
|
|
481
|
+
} catch (err) {
|
|
482
|
+
this.rethrowGrpcError(err, { workflowId, runId }, 'Failed to get Workflow execution history');
|
|
483
|
+
}
|
|
484
|
+
if (!res.history) {
|
|
485
|
+
throw new Error('No history returned by service');
|
|
486
|
+
}
|
|
487
|
+
const { events } = res.history;
|
|
488
|
+
if (!events) {
|
|
489
|
+
throw new Error('No events in history returned by service');
|
|
490
|
+
}
|
|
491
|
+
if (events.length === 0) {
|
|
492
|
+
req.nextPageToken = res.nextPageToken;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (events.length !== 1) {
|
|
496
|
+
throw new Error(`Expected at most 1 close event(s), got: ${events.length}`);
|
|
497
|
+
}
|
|
498
|
+
ev = events[0];
|
|
499
|
+
|
|
500
|
+
if (ev.workflowExecutionCompletedEventAttributes) {
|
|
501
|
+
if (followRuns && ev.workflowExecutionCompletedEventAttributes.newExecutionRunId) {
|
|
502
|
+
execution.runId = ev.workflowExecutionCompletedEventAttributes.newExecutionRunId;
|
|
503
|
+
req.nextPageToken = undefined;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
// Note that we can only return one value from our workflow function in JS.
|
|
507
|
+
// Ignore any other payloads in result
|
|
508
|
+
const [result] = await decodeArrayFromPayloads(
|
|
509
|
+
this.options.loadedDataConverter,
|
|
510
|
+
ev.workflowExecutionCompletedEventAttributes.result?.payloads
|
|
511
|
+
);
|
|
512
|
+
return result as any;
|
|
513
|
+
} else if (ev.workflowExecutionFailedEventAttributes) {
|
|
514
|
+
if (followRuns && ev.workflowExecutionFailedEventAttributes.newExecutionRunId) {
|
|
515
|
+
execution.runId = ev.workflowExecutionFailedEventAttributes.newExecutionRunId;
|
|
516
|
+
req.nextPageToken = undefined;
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const { failure, retryState } = ev.workflowExecutionFailedEventAttributes;
|
|
520
|
+
throw new WorkflowFailedError(
|
|
521
|
+
'Workflow execution failed',
|
|
522
|
+
await decodeOptionalFailureToOptionalError(this.options.loadedDataConverter, failure),
|
|
523
|
+
retryState ?? RetryState.RETRY_STATE_UNSPECIFIED
|
|
524
|
+
);
|
|
525
|
+
} else if (ev.workflowExecutionCanceledEventAttributes) {
|
|
526
|
+
const failure = new CancelledFailure(
|
|
527
|
+
'Workflow canceled',
|
|
528
|
+
await decodeArrayFromPayloads(
|
|
529
|
+
this.options.loadedDataConverter,
|
|
530
|
+
ev.workflowExecutionCanceledEventAttributes.details?.payloads
|
|
531
|
+
)
|
|
532
|
+
);
|
|
533
|
+
failure.stack = '';
|
|
534
|
+
throw new WorkflowFailedError(
|
|
535
|
+
'Workflow execution cancelled',
|
|
536
|
+
failure,
|
|
537
|
+
RetryState.RETRY_STATE_NON_RETRYABLE_FAILURE
|
|
538
|
+
);
|
|
539
|
+
} else if (ev.workflowExecutionTerminatedEventAttributes) {
|
|
540
|
+
const failure = new TerminatedFailure(
|
|
541
|
+
ev.workflowExecutionTerminatedEventAttributes.reason || 'Workflow execution terminated'
|
|
542
|
+
);
|
|
543
|
+
failure.stack = '';
|
|
544
|
+
throw new WorkflowFailedError(
|
|
545
|
+
ev.workflowExecutionTerminatedEventAttributes.reason || 'Workflow execution terminated',
|
|
546
|
+
failure,
|
|
547
|
+
RetryState.RETRY_STATE_NON_RETRYABLE_FAILURE
|
|
548
|
+
);
|
|
549
|
+
} else if (ev.workflowExecutionTimedOutEventAttributes) {
|
|
550
|
+
if (followRuns && ev.workflowExecutionTimedOutEventAttributes.newExecutionRunId) {
|
|
551
|
+
execution.runId = ev.workflowExecutionTimedOutEventAttributes.newExecutionRunId;
|
|
552
|
+
req.nextPageToken = undefined;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const failure = new TimeoutFailure(
|
|
556
|
+
'Workflow execution timed out',
|
|
557
|
+
undefined,
|
|
558
|
+
TimeoutType.TIMEOUT_TYPE_START_TO_CLOSE
|
|
559
|
+
);
|
|
560
|
+
failure.stack = '';
|
|
561
|
+
throw new WorkflowFailedError(
|
|
562
|
+
'Workflow execution timed out',
|
|
563
|
+
failure,
|
|
564
|
+
ev.workflowExecutionTimedOutEventAttributes.retryState || 0
|
|
565
|
+
);
|
|
566
|
+
} else if (ev.workflowExecutionContinuedAsNewEventAttributes) {
|
|
567
|
+
const { newExecutionRunId } = ev.workflowExecutionContinuedAsNewEventAttributes;
|
|
568
|
+
if (!newExecutionRunId) {
|
|
569
|
+
throw new TypeError('Expected service to return newExecutionRunId for WorkflowExecutionContinuedAsNewEvent');
|
|
570
|
+
}
|
|
571
|
+
if (!followRuns) {
|
|
572
|
+
throw new WorkflowContinuedAsNewError('Workflow execution continued as new', newExecutionRunId);
|
|
573
|
+
}
|
|
574
|
+
execution.runId = newExecutionRunId;
|
|
575
|
+
req.nextPageToken = undefined;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
protected rethrowGrpcError(err: unknown, workflowExecution: WorkflowExecution, fallbackMessage: string): never {
|
|
582
|
+
if (isServerErrorResponse(err)) {
|
|
583
|
+
if (err.code === grpcStatus.NOT_FOUND) {
|
|
584
|
+
throw new WorkflowNotFoundError(
|
|
585
|
+
err.details ?? 'Workflow not found',
|
|
586
|
+
workflowExecution.workflowId,
|
|
587
|
+
workflowExecution.runId
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
throw new ServiceError(fallbackMessage, { cause: err });
|
|
591
|
+
}
|
|
592
|
+
throw new ServiceError('Unexpected error while making gRPC request');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Uses given input to make a queryWorkflow call to the service
|
|
597
|
+
*
|
|
598
|
+
* Used as the final function of the query interceptor chain
|
|
599
|
+
*/
|
|
600
|
+
protected async _queryWorkflowHandler(input: WorkflowQueryInput): Promise<unknown> {
|
|
601
|
+
let response: temporal.api.workflowservice.v1.QueryWorkflowResponse;
|
|
602
|
+
try {
|
|
603
|
+
response = await this.workflowService.queryWorkflow({
|
|
604
|
+
queryRejectCondition: input.queryRejectCondition,
|
|
605
|
+
namespace: this.options.namespace,
|
|
606
|
+
execution: input.workflowExecution,
|
|
607
|
+
query: {
|
|
608
|
+
queryType: input.queryType,
|
|
609
|
+
queryArgs: { payloads: await encodeToPayloads(this.options.loadedDataConverter, ...input.args) },
|
|
610
|
+
header: { fields: input.headers },
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
} catch (err) {
|
|
614
|
+
this.rethrowGrpcError(err, input.workflowExecution, 'Failed to query Workflow');
|
|
615
|
+
}
|
|
616
|
+
if (response.queryRejected) {
|
|
617
|
+
if (response.queryRejected.status === undefined || response.queryRejected.status === null) {
|
|
618
|
+
throw new TypeError('Received queryRejected from server with no status');
|
|
619
|
+
}
|
|
620
|
+
throw new QueryRejectedError(response.queryRejected.status);
|
|
621
|
+
}
|
|
622
|
+
if (!response.queryResult) {
|
|
623
|
+
throw new TypeError('Invalid response from server');
|
|
624
|
+
}
|
|
625
|
+
// We ignore anything but the first result
|
|
626
|
+
return await decodeFromPayloadsAtIndex(this.options.loadedDataConverter, 0, response.queryResult?.payloads);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Uses given input to make a signalWorkflowExecution call to the service
|
|
631
|
+
*
|
|
632
|
+
* Used as the final function of the signal interceptor chain
|
|
633
|
+
*/
|
|
634
|
+
protected async _signalWorkflowHandler(input: WorkflowSignalInput): Promise<void> {
|
|
635
|
+
try {
|
|
636
|
+
await this.workflowService.signalWorkflowExecution({
|
|
637
|
+
identity: this.options.identity,
|
|
638
|
+
namespace: this.options.namespace,
|
|
639
|
+
workflowExecution: input.workflowExecution,
|
|
640
|
+
requestId: uuid4(),
|
|
641
|
+
// control is unused,
|
|
642
|
+
signalName: input.signalName,
|
|
643
|
+
header: { fields: input.headers },
|
|
644
|
+
input: { payloads: await encodeToPayloads(this.options.loadedDataConverter, ...input.args) },
|
|
645
|
+
});
|
|
646
|
+
} catch (err) {
|
|
647
|
+
this.rethrowGrpcError(err, input.workflowExecution, 'Failed to signal Workflow');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Uses given input to make a signalWithStartWorkflowExecution call to the service
|
|
653
|
+
*
|
|
654
|
+
* Used as the final function of the signalWithStart interceptor chain
|
|
655
|
+
*/
|
|
656
|
+
protected async _signalWithStartWorkflowHandler(input: WorkflowSignalWithStartInput): Promise<string> {
|
|
657
|
+
const { identity } = this.options;
|
|
658
|
+
const { options, workflowType, signalName, signalArgs, headers } = input;
|
|
659
|
+
try {
|
|
660
|
+
const { runId } = await this.workflowService.signalWithStartWorkflowExecution({
|
|
661
|
+
namespace: this.options.namespace,
|
|
662
|
+
identity,
|
|
663
|
+
requestId: uuid4(),
|
|
664
|
+
workflowId: options.workflowId,
|
|
665
|
+
workflowIdReusePolicy: options.workflowIdReusePolicy,
|
|
666
|
+
workflowType: { name: workflowType },
|
|
667
|
+
input: { payloads: await encodeToPayloads(this.options.loadedDataConverter, ...options.args) },
|
|
668
|
+
signalName,
|
|
669
|
+
signalInput: { payloads: await encodeToPayloads(this.options.loadedDataConverter, ...signalArgs) },
|
|
670
|
+
taskQueue: {
|
|
671
|
+
kind: temporal.api.enums.v1.TaskQueueKind.TASK_QUEUE_KIND_UNSPECIFIED,
|
|
672
|
+
name: options.taskQueue,
|
|
673
|
+
},
|
|
674
|
+
workflowExecutionTimeout: options.workflowExecutionTimeout,
|
|
675
|
+
workflowRunTimeout: options.workflowRunTimeout,
|
|
676
|
+
workflowTaskTimeout: options.workflowTaskTimeout,
|
|
677
|
+
retryPolicy: options.retry ? compileRetryPolicy(options.retry) : undefined,
|
|
678
|
+
memo: options.memo
|
|
679
|
+
? { fields: await encodeMapToPayloads(this.options.loadedDataConverter, options.memo) }
|
|
680
|
+
: undefined,
|
|
681
|
+
searchAttributes: options.searchAttributes
|
|
682
|
+
? {
|
|
683
|
+
indexedFields: mapToPayloads(searchAttributePayloadConverter, options.searchAttributes),
|
|
684
|
+
}
|
|
685
|
+
: undefined,
|
|
686
|
+
cronSchedule: options.cronSchedule,
|
|
687
|
+
header: { fields: headers },
|
|
688
|
+
});
|
|
689
|
+
return runId;
|
|
690
|
+
} catch (err) {
|
|
691
|
+
this.rethrowGrpcError(err, { workflowId: options.workflowId }, 'Failed to signalWithStart Workflow');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Uses given input to make startWorkflowExecution call to the service
|
|
697
|
+
*
|
|
698
|
+
* Used as the final function of the start interceptor chain
|
|
699
|
+
*/
|
|
700
|
+
protected async _startWorkflowHandler(input: WorkflowStartInput): Promise<string> {
|
|
701
|
+
const { options: opts, workflowType, headers } = input;
|
|
702
|
+
const { identity } = this.options;
|
|
703
|
+
const req: StartWorkflowExecutionRequest = {
|
|
704
|
+
namespace: this.options.namespace,
|
|
705
|
+
identity,
|
|
706
|
+
requestId: uuid4(),
|
|
707
|
+
workflowId: opts.workflowId,
|
|
708
|
+
workflowIdReusePolicy: opts.workflowIdReusePolicy,
|
|
709
|
+
workflowType: { name: workflowType },
|
|
710
|
+
input: { payloads: await encodeToPayloads(this.options.loadedDataConverter, ...opts.args) },
|
|
711
|
+
taskQueue: {
|
|
712
|
+
kind: temporal.api.enums.v1.TaskQueueKind.TASK_QUEUE_KIND_UNSPECIFIED,
|
|
713
|
+
name: opts.taskQueue,
|
|
714
|
+
},
|
|
715
|
+
workflowExecutionTimeout: opts.workflowExecutionTimeout,
|
|
716
|
+
workflowRunTimeout: opts.workflowRunTimeout,
|
|
717
|
+
workflowTaskTimeout: opts.workflowTaskTimeout,
|
|
718
|
+
retryPolicy: opts.retry ? compileRetryPolicy(opts.retry) : undefined,
|
|
719
|
+
memo: opts.memo ? { fields: await encodeMapToPayloads(this.options.loadedDataConverter, opts.memo) } : undefined,
|
|
720
|
+
searchAttributes: opts.searchAttributes
|
|
721
|
+
? {
|
|
722
|
+
indexedFields: mapToPayloads(searchAttributePayloadConverter, opts.searchAttributes),
|
|
723
|
+
}
|
|
724
|
+
: undefined,
|
|
725
|
+
cronSchedule: opts.cronSchedule,
|
|
726
|
+
header: { fields: headers },
|
|
727
|
+
};
|
|
728
|
+
try {
|
|
729
|
+
const res = await this.workflowService.startWorkflowExecution(req);
|
|
730
|
+
return res.runId;
|
|
731
|
+
} catch (err: any) {
|
|
732
|
+
if (err.code === grpcStatus.ALREADY_EXISTS) {
|
|
733
|
+
throw new WorkflowExecutionAlreadyStartedError(
|
|
734
|
+
'Workflow execution already started',
|
|
735
|
+
opts.workflowId,
|
|
736
|
+
workflowType
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
this.rethrowGrpcError(err, { workflowId: opts.workflowId }, 'Failed to start Workflow');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Uses given input to make terminateWorkflowExecution call to the service
|
|
745
|
+
*
|
|
746
|
+
* Used as the final function of the terminate interceptor chain
|
|
747
|
+
*/
|
|
748
|
+
protected async _terminateWorkflowHandler(
|
|
749
|
+
input: WorkflowTerminateInput
|
|
750
|
+
): Promise<TerminateWorkflowExecutionResponse> {
|
|
751
|
+
try {
|
|
752
|
+
return await this.workflowService.terminateWorkflowExecution({
|
|
753
|
+
namespace: this.options.namespace,
|
|
754
|
+
identity: this.options.identity,
|
|
755
|
+
...input,
|
|
756
|
+
details: {
|
|
757
|
+
payloads: input.details
|
|
758
|
+
? await encodeToPayloads(this.options.loadedDataConverter, ...input.details)
|
|
759
|
+
: undefined,
|
|
760
|
+
},
|
|
761
|
+
firstExecutionRunId: input.firstExecutionRunId,
|
|
762
|
+
});
|
|
763
|
+
} catch (err) {
|
|
764
|
+
this.rethrowGrpcError(err, input.workflowExecution, 'Failed to terminate Workflow');
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Uses given input to make requestCancelWorkflowExecution call to the service
|
|
770
|
+
*
|
|
771
|
+
* Used as the final function of the cancel interceptor chain
|
|
772
|
+
*/
|
|
773
|
+
protected async _cancelWorkflowHandler(input: WorkflowCancelInput): Promise<RequestCancelWorkflowExecutionResponse> {
|
|
774
|
+
try {
|
|
775
|
+
return await this.workflowService.requestCancelWorkflowExecution({
|
|
776
|
+
namespace: this.options.namespace,
|
|
777
|
+
identity: this.options.identity,
|
|
778
|
+
requestId: uuid4(),
|
|
779
|
+
workflowExecution: input.workflowExecution,
|
|
780
|
+
firstExecutionRunId: input.firstExecutionRunId,
|
|
781
|
+
});
|
|
782
|
+
} catch (err) {
|
|
783
|
+
this.rethrowGrpcError(err, input.workflowExecution, 'Failed to cancel workflow');
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Uses given input to make describeWorkflowExecution call to the service
|
|
789
|
+
*
|
|
790
|
+
* Used as the final function of the describe interceptor chain
|
|
791
|
+
*/
|
|
792
|
+
protected async _describeWorkflowHandler(input: WorkflowDescribeInput): Promise<DescribeWorkflowExecutionResponse> {
|
|
793
|
+
try {
|
|
794
|
+
return await this.workflowService.describeWorkflowExecution({
|
|
795
|
+
namespace: this.options.namespace,
|
|
796
|
+
execution: input.workflowExecution,
|
|
797
|
+
});
|
|
798
|
+
} catch (err) {
|
|
799
|
+
this.rethrowGrpcError(err, input.workflowExecution, 'Failed to describe workflow');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Create a new workflow handle for new or existing Workflow execution
|
|
805
|
+
*/
|
|
806
|
+
protected _createWorkflowHandle<T extends Workflow>({
|
|
807
|
+
workflowId,
|
|
808
|
+
runId,
|
|
809
|
+
firstExecutionRunId,
|
|
810
|
+
interceptors,
|
|
811
|
+
runIdForResult,
|
|
812
|
+
...resultOptions
|
|
813
|
+
}: WorkflowHandleOptions): WorkflowHandle<T> {
|
|
814
|
+
return {
|
|
815
|
+
client: this,
|
|
816
|
+
workflowId,
|
|
817
|
+
async result(): Promise<WorkflowResultType<T>> {
|
|
818
|
+
return this.client.result(workflowId, runIdForResult, resultOptions);
|
|
819
|
+
},
|
|
820
|
+
async terminate(reason?: string) {
|
|
821
|
+
const next = this.client._terminateWorkflowHandler.bind(this.client);
|
|
822
|
+
const fn = interceptors.length ? composeInterceptors(interceptors, 'terminate', next) : next;
|
|
823
|
+
return await fn({
|
|
824
|
+
workflowExecution: { workflowId, runId },
|
|
825
|
+
reason,
|
|
826
|
+
firstExecutionRunId,
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
async cancel() {
|
|
830
|
+
const next = this.client._cancelWorkflowHandler.bind(this.client);
|
|
831
|
+
const fn = interceptors.length ? composeInterceptors(interceptors, 'cancel', next) : next;
|
|
832
|
+
return await fn({
|
|
833
|
+
workflowExecution: { workflowId, runId },
|
|
834
|
+
firstExecutionRunId,
|
|
835
|
+
});
|
|
836
|
+
},
|
|
837
|
+
async describe() {
|
|
838
|
+
const next = this.client._describeWorkflowHandler.bind(this.client);
|
|
839
|
+
const fn = interceptors.length ? composeInterceptors(interceptors, 'describe', next) : next;
|
|
840
|
+
const raw = await fn({
|
|
841
|
+
workflowExecution: { workflowId, runId },
|
|
842
|
+
});
|
|
843
|
+
return {
|
|
844
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
845
|
+
type: raw.workflowExecutionInfo!.type!.name!,
|
|
846
|
+
workflowId: raw.workflowExecutionInfo!.execution!.workflowId!,
|
|
847
|
+
runId: raw.workflowExecutionInfo!.execution!.runId!,
|
|
848
|
+
taskQueue: raw.workflowExecutionInfo!.taskQueue!,
|
|
849
|
+
status: {
|
|
850
|
+
code: raw.workflowExecutionInfo!.status!,
|
|
851
|
+
name: workflowStatusCodeToName(raw.workflowExecutionInfo!.status!),
|
|
852
|
+
},
|
|
853
|
+
historyLength: raw.workflowExecutionInfo!.historyLength!,
|
|
854
|
+
startTime: tsToDate(raw.workflowExecutionInfo!.startTime!),
|
|
855
|
+
executionTime: optionalTsToDate(raw.workflowExecutionInfo!.executionTime),
|
|
856
|
+
closeTime: optionalTsToDate(raw.workflowExecutionInfo!.closeTime),
|
|
857
|
+
memo: await decodeMapFromPayloads(
|
|
858
|
+
this.client.options.loadedDataConverter,
|
|
859
|
+
raw.workflowExecutionInfo!.memo?.fields
|
|
860
|
+
),
|
|
861
|
+
searchAttributes: mapFromPayloads(
|
|
862
|
+
searchAttributePayloadConverter,
|
|
863
|
+
raw.workflowExecutionInfo!.searchAttributes?.indexedFields ?? {}
|
|
864
|
+
) as SearchAttributes,
|
|
865
|
+
parentExecution: raw.workflowExecutionInfo?.parentExecution
|
|
866
|
+
? {
|
|
867
|
+
workflowId: raw.workflowExecutionInfo.parentExecution.workflowId!,
|
|
868
|
+
runId: raw.workflowExecutionInfo.parentExecution.runId!,
|
|
869
|
+
}
|
|
870
|
+
: undefined,
|
|
871
|
+
raw,
|
|
872
|
+
};
|
|
873
|
+
},
|
|
874
|
+
async signal<Args extends any[]>(def: SignalDefinition<Args> | string, ...args: Args): Promise<void> {
|
|
875
|
+
const next = this.client._signalWorkflowHandler.bind(this.client);
|
|
876
|
+
const fn = interceptors.length ? composeInterceptors(interceptors, 'signal', next) : next;
|
|
877
|
+
await fn({
|
|
878
|
+
workflowExecution: { workflowId, runId },
|
|
879
|
+
signalName: typeof def === 'string' ? def : def.name,
|
|
880
|
+
args,
|
|
881
|
+
headers: {},
|
|
882
|
+
});
|
|
883
|
+
},
|
|
884
|
+
async query<Ret, Args extends any[]>(def: QueryDefinition<Ret, Args> | string, ...args: Args): Promise<Ret> {
|
|
885
|
+
const next = this.client._queryWorkflowHandler.bind(this.client);
|
|
886
|
+
const fn = interceptors.length ? composeInterceptors(interceptors, 'query', next) : next;
|
|
887
|
+
return fn({
|
|
888
|
+
workflowExecution: { workflowId, runId },
|
|
889
|
+
queryRejectCondition: this.client.options.queryRejectCondition,
|
|
890
|
+
queryType: typeof def === 'string' ? def : def.name,
|
|
891
|
+
args,
|
|
892
|
+
headers: {},
|
|
893
|
+
}) as Promise<Ret>;
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Create a handle to an existing Workflow.
|
|
900
|
+
*
|
|
901
|
+
* - If only `workflowId` is passed, and there are multiple Workflow Executions with that ID, the handle will refer to
|
|
902
|
+
* the most recent one.
|
|
903
|
+
* - If `workflowId` and `runId` are passed, the handle will refer to the specific Workflow Execution with that Run
|
|
904
|
+
* ID.
|
|
905
|
+
* - If `workflowId` and {@link GetWorkflowHandleOptions.firstExecutionRunId} are passed, the handle will refer to the
|
|
906
|
+
* most recent Workflow Execution in the *Chain* that started with `firstExecutionRunId`.
|
|
907
|
+
*
|
|
908
|
+
* A *Chain* is a series of Workflow Executions that share the same Workflow ID and are connected by:
|
|
909
|
+
* - Being part of the same {@link https://docs.temporal.io/typescript/clients#scheduling-cron-workflows | Cron}
|
|
910
|
+
* - {@link https://docs.temporal.io/typescript/workflows#continueasnew | Continue As New}
|
|
911
|
+
* - {@link https://typescript.temporal.io/api/interfaces/client.workflowoptions/#retry | Retries}
|
|
912
|
+
*
|
|
913
|
+
* This method does not validate `workflowId`. If there is no Workflow Execution with the given `workflowId`, handle
|
|
914
|
+
* methods like `handle.describe()` will throw a {@link WorkflowNotFoundError} error.
|
|
915
|
+
*/
|
|
916
|
+
public getHandle<T extends Workflow>(
|
|
917
|
+
workflowId: string,
|
|
918
|
+
runId?: string,
|
|
919
|
+
options?: GetWorkflowHandleOptions
|
|
920
|
+
): WorkflowHandle<T> {
|
|
921
|
+
const interceptors = (this.options.interceptors.calls ?? []).map((ctor) => ctor({ workflowId, runId }));
|
|
922
|
+
|
|
923
|
+
return this._createWorkflowHandle({
|
|
924
|
+
workflowId,
|
|
925
|
+
runId,
|
|
926
|
+
firstExecutionRunId: options?.firstExecutionRunId,
|
|
927
|
+
runIdForResult: runId ?? options?.firstExecutionRunId,
|
|
928
|
+
interceptors,
|
|
929
|
+
followRuns: options?.followRuns ?? true,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
export class QueryRejectedError extends Error {
|
|
935
|
+
public readonly name: string = 'QueryRejectedError';
|
|
936
|
+
constructor(public readonly status: temporal.api.enums.v1.WorkflowExecutionStatus) {
|
|
937
|
+
super('Query rejected');
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function workflowStatusCodeToName(code: temporal.api.enums.v1.WorkflowExecutionStatus): WorkflowExecutionStatusName {
|
|
942
|
+
return workflowStatusCodeToNameInternal(code) ?? 'UNKNOWN';
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Intentionally leave out `default` branch to get compilation errors when new values are added
|
|
947
|
+
*/
|
|
948
|
+
function workflowStatusCodeToNameInternal(
|
|
949
|
+
code: temporal.api.enums.v1.WorkflowExecutionStatus
|
|
950
|
+
): WorkflowExecutionStatusName {
|
|
951
|
+
switch (code) {
|
|
952
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_UNSPECIFIED:
|
|
953
|
+
return 'UNSPECIFIED';
|
|
954
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING:
|
|
955
|
+
return 'RUNNING';
|
|
956
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_FAILED:
|
|
957
|
+
return 'FAILED';
|
|
958
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_TIMED_OUT:
|
|
959
|
+
return 'TIMED_OUT';
|
|
960
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_CANCELED:
|
|
961
|
+
return 'CANCELLED';
|
|
962
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_TERMINATED:
|
|
963
|
+
return 'TERMINATED';
|
|
964
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED:
|
|
965
|
+
return 'COMPLETED';
|
|
966
|
+
case temporal.api.enums.v1.WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW:
|
|
967
|
+
return 'CONTINUED_AS_NEW';
|
|
968
|
+
}
|
|
969
|
+
}
|