@temporalio/client 1.0.0-rc.0 → 1.0.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.
@@ -0,0 +1,256 @@
1
+ import { Status } from '@grpc/grpc-js/build/src/constants';
2
+ import { DataConverter, ensureTemporalFailure, LoadedDataConverter } from '@temporalio/common';
3
+ import {
4
+ encodeErrorToFailure,
5
+ encodeToPayloads,
6
+ filterNullAndUndefined,
7
+ loadDataConverter,
8
+ } from '@temporalio/internal-non-workflow-common';
9
+ import { Replace } from '@temporalio/internal-workflow-common';
10
+ import os from 'os';
11
+ import { Connection } from './connection';
12
+ import { isServerErrorResponse } from './errors';
13
+ import { ConnectionLike, WorkflowService } from './types';
14
+
15
+ /**
16
+ * Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat an Activity that does not exist in the
17
+ * system.
18
+ */
19
+ export class ActivityNotFoundError extends Error {
20
+ public readonly name = 'ActivityNotFoundError';
21
+ }
22
+
23
+ /**
24
+ * Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat
25
+ * an Activity for any reason apart from {@link ActivityNotFoundError}.
26
+ */
27
+ export class ActivityCompletionError extends Error {
28
+ public readonly name = 'ActivityCompletionError';
29
+ }
30
+
31
+ /**
32
+ * Thrown by {@link AsyncCompletionClient.heartbeat} when the Workflow has
33
+ * requested to cancel the reporting Activity.
34
+ */
35
+ export class ActivityCancelledError extends Error {
36
+ public readonly name = 'ActivityCancelledError';
37
+ }
38
+
39
+ /**
40
+ * Options used to configure {@link AsyncCompletionClient}
41
+ */
42
+ export interface AsyncCompletionClientOptions {
43
+ /**
44
+ * {@link DataConverter} to use for serializing and deserializing payloads
45
+ */
46
+ dataConverter?: DataConverter;
47
+
48
+ /**
49
+ * Identity to report to the server
50
+ *
51
+ * @default `${process.pid}@${os.hostname()}`
52
+ */
53
+ identity?: string;
54
+
55
+ connection?: ConnectionLike;
56
+
57
+ /**
58
+ * Server namespace
59
+ *
60
+ * @default default
61
+ */
62
+ namespace?: string;
63
+ }
64
+
65
+ export type AsyncCompletionClientOptionsWithDefaults = Replace<
66
+ Required<AsyncCompletionClientOptions>,
67
+ {
68
+ connection?: ConnectionLike;
69
+ }
70
+ >;
71
+
72
+ export function defaultAsyncCompletionClientOptions(): AsyncCompletionClientOptionsWithDefaults {
73
+ return {
74
+ dataConverter: {},
75
+ identity: `${process.pid}@${os.hostname()}`,
76
+ namespace: 'default',
77
+ };
78
+ }
79
+
80
+ /**
81
+ * A mostly unique Activity identifier including its scheduling workflow's ID
82
+ * and an optional runId.
83
+ *
84
+ * Activity IDs may be reused in a single Workflow run as long as a previous
85
+ * Activity with the same ID has completed already.
86
+ */
87
+ export interface FullActivityId {
88
+ workflowId: string;
89
+ runId?: string;
90
+ activityId: string;
91
+ }
92
+
93
+ /**
94
+ * A client for asynchronous completion and heartbeating of Activities.
95
+ */
96
+ export class AsyncCompletionClient {
97
+ public readonly options: AsyncCompletionClientOptionsWithDefaults;
98
+ protected readonly dataConverter: LoadedDataConverter;
99
+ public readonly connection: ConnectionLike;
100
+
101
+ constructor(options?: AsyncCompletionClientOptions) {
102
+ this.connection = options?.connection ?? Connection.lazy();
103
+ this.dataConverter = loadDataConverter(options?.dataConverter);
104
+ this.options = { ...defaultAsyncCompletionClientOptions(), ...filterNullAndUndefined(options ?? {}) };
105
+ }
106
+
107
+ get workflowService(): WorkflowService {
108
+ return this.connection.workflowService;
109
+ }
110
+
111
+ /**
112
+ * Transforms grpc errors into well defined TS errors.
113
+ */
114
+ protected handleError(err: unknown): never {
115
+ if (isServerErrorResponse(err)) {
116
+ if (err.code === Status.NOT_FOUND) {
117
+ throw new ActivityNotFoundError('Not found');
118
+ }
119
+ throw new ActivityCompletionError(err.details || err.message);
120
+ }
121
+ throw new ActivityCompletionError('Unexpected failure');
122
+ }
123
+
124
+ /**
125
+ * Complete an Activity by task token
126
+ */
127
+ async complete(taskToken: Uint8Array, result: unknown): Promise<void>;
128
+ /**
129
+ * Complete an Activity by full ID
130
+ */
131
+ async complete(fullActivityId: FullActivityId, result: unknown): Promise<void>;
132
+
133
+ async complete(taskTokenOrFullActivityId: Uint8Array | FullActivityId, result: unknown): Promise<void> {
134
+ try {
135
+ if (taskTokenOrFullActivityId instanceof Uint8Array) {
136
+ await this.workflowService.respondActivityTaskCompleted({
137
+ identity: this.options.identity,
138
+ namespace: this.options.namespace,
139
+ taskToken: taskTokenOrFullActivityId,
140
+ result: { payloads: await encodeToPayloads(this.dataConverter, result) },
141
+ });
142
+ } else {
143
+ await this.workflowService.respondActivityTaskCompletedById({
144
+ identity: this.options.identity,
145
+ namespace: this.options.namespace,
146
+ ...taskTokenOrFullActivityId,
147
+ result: { payloads: await encodeToPayloads(this.dataConverter, result) },
148
+ });
149
+ }
150
+ } catch (err) {
151
+ this.handleError(err);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Fail an Activity by task token
157
+ */
158
+ async fail(taskToken: Uint8Array, err: unknown): Promise<void>;
159
+ /**
160
+ * Fail an Activity by full ID
161
+ */
162
+ async fail(fullActivityId: FullActivityId, err: unknown): Promise<void>;
163
+
164
+ async fail(taskTokenOrFullActivityId: Uint8Array | FullActivityId, err: unknown): Promise<void> {
165
+ try {
166
+ if (taskTokenOrFullActivityId instanceof Uint8Array) {
167
+ await this.workflowService.respondActivityTaskFailed({
168
+ identity: this.options.identity,
169
+ namespace: this.options.namespace,
170
+ taskToken: taskTokenOrFullActivityId,
171
+ failure: await encodeErrorToFailure(this.dataConverter, ensureTemporalFailure(err)),
172
+ });
173
+ } else {
174
+ await this.workflowService.respondActivityTaskFailedById({
175
+ identity: this.options.identity,
176
+ namespace: this.options.namespace,
177
+ ...taskTokenOrFullActivityId,
178
+ failure: await encodeErrorToFailure(this.dataConverter, err),
179
+ });
180
+ }
181
+ } catch (err) {
182
+ this.handleError(err);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Report Activity cancellation by task token
188
+ */
189
+ reportCancellation(taskToken: Uint8Array, details?: unknown): Promise<void>;
190
+ /**
191
+ * Report Activity cancellation by full ID
192
+ */
193
+ reportCancellation(fullActivityId: FullActivityId, details?: unknown): Promise<void>;
194
+
195
+ async reportCancellation(taskTokenOrFullActivityId: Uint8Array | FullActivityId, details?: unknown): Promise<void> {
196
+ try {
197
+ if (taskTokenOrFullActivityId instanceof Uint8Array) {
198
+ await this.workflowService.respondActivityTaskCanceled({
199
+ identity: this.options.identity,
200
+ namespace: this.options.namespace,
201
+ taskToken: taskTokenOrFullActivityId,
202
+ details: { payloads: await encodeToPayloads(this.dataConverter, details) },
203
+ });
204
+ } else {
205
+ await this.workflowService.respondActivityTaskCanceledById({
206
+ identity: this.options.identity,
207
+ namespace: this.options.namespace,
208
+ ...taskTokenOrFullActivityId,
209
+ details: { payloads: await encodeToPayloads(this.dataConverter, details) },
210
+ });
211
+ }
212
+ } catch (err) {
213
+ this.handleError(err);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Send Activity heartbeat by task token
219
+ */
220
+ heartbeat(taskToken: Uint8Array, details?: unknown): Promise<void>;
221
+ /**
222
+ * Send Activity heartbeat by full ID
223
+ */
224
+ heartbeat(fullActivityId: FullActivityId, details?: unknown): Promise<void>;
225
+
226
+ async heartbeat(taskTokenOrFullActivityId: Uint8Array | FullActivityId, details?: unknown): Promise<void> {
227
+ try {
228
+ if (taskTokenOrFullActivityId instanceof Uint8Array) {
229
+ const { cancelRequested } = await this.workflowService.recordActivityTaskHeartbeat({
230
+ identity: this.options.identity,
231
+ namespace: this.options.namespace,
232
+ taskToken: taskTokenOrFullActivityId,
233
+ details: { payloads: await encodeToPayloads(this.dataConverter, details) },
234
+ });
235
+ if (cancelRequested) {
236
+ throw new ActivityCancelledError('cancelled');
237
+ }
238
+ } else {
239
+ const { cancelRequested } = await this.workflowService.recordActivityTaskHeartbeatById({
240
+ identity: this.options.identity,
241
+ namespace: this.options.namespace,
242
+ ...taskTokenOrFullActivityId,
243
+ details: { payloads: await encodeToPayloads(this.dataConverter, details) },
244
+ });
245
+ if (cancelRequested) {
246
+ throw new ActivityCancelledError('cancelled');
247
+ }
248
+ }
249
+ } catch (err) {
250
+ if (err instanceof ActivityCancelledError) {
251
+ throw err;
252
+ }
253
+ this.handleError(err);
254
+ }
255
+ }
256
+ }
@@ -0,0 +1,374 @@
1
+ import * as grpc from '@grpc/grpc-js';
2
+ import { filterNullAndUndefined, normalizeTlsConfig, TLSConfig } from '@temporalio/internal-non-workflow-common';
3
+ import { AsyncLocalStorage } from 'async_hooks';
4
+ import type { RPCImpl } from 'protobufjs';
5
+ import { isServerErrorResponse, ServiceError } from './errors';
6
+ import { defaultGrpcRetryOptions, makeGrpcRetryInterceptor } from './grpc-retry';
7
+ import pkg from './pkg';
8
+ import { CallContext, Metadata, OperatorService, WorkflowService } from './types';
9
+
10
+ /**
11
+ * gRPC and Temporal Server connection options
12
+ */
13
+ export interface ConnectionOptions {
14
+ /**
15
+ * Server hostname and optional port.
16
+ * Port defaults to 7233 if address contains only host.
17
+ *
18
+ * @default localhost:7233
19
+ */
20
+ address?: string;
21
+
22
+ /**
23
+ * TLS configuration.
24
+ * Pass a falsy value to use a non-encrypted connection or `true` or `{}` to
25
+ * connect with TLS without any customization.
26
+ *
27
+ * Either {@link credentials} or this may be specified for configuring TLS
28
+ */
29
+ tls?: TLSConfig | boolean | null;
30
+
31
+ /**
32
+ * Channel credentials, create using the factory methods defined {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
33
+ *
34
+ * Either {@link tls} or this may be specified for configuring TLS
35
+ */
36
+ credentials?: grpc.ChannelCredentials;
37
+
38
+ /**
39
+ * GRPC Channel arguments
40
+ *
41
+ * @see option descriptions {@link https://grpc.github.io/grpc/core/group__grpc__arg__keys.html | here}
42
+ */
43
+ channelArgs?: grpc.ChannelOptions;
44
+
45
+ /**
46
+ * Grpc interceptors which will be applied to every RPC call performed by this connection. By
47
+ * default, an interceptor will be included which automatically retries retryable errors. If you
48
+ * do not wish to perform automatic retries, set this to an empty list (or a list with your own
49
+ * interceptors).
50
+ */
51
+ interceptors?: grpc.Interceptor[];
52
+
53
+ /**
54
+ * Optional mapping of gRPC metadata (HTTP headers) to send with each request to the server.
55
+ *
56
+ * In order to dynamically set metadata, use {@link Connection.withMetadata}
57
+ */
58
+ metadata?: Metadata;
59
+
60
+ /**
61
+ * Milliseconds to wait until establishing a connection with the server.
62
+ *
63
+ * Used either when connecting eagerly with {@link Connection.connect} or
64
+ * calling {@link Connection.ensureConnected}.
65
+ *
66
+ * @format {@link https://www.npmjs.com/package/ms | ms} formatted string
67
+ * @default 10 seconds
68
+ */
69
+ connectTimeout?: number | string;
70
+ }
71
+
72
+ export type ConnectionOptionsWithDefaults = Required<Omit<ConnectionOptions, 'tls' | 'connectTimeout'>> & {
73
+ connectTimeoutMs: number;
74
+ };
75
+
76
+ export const LOCAL_TARGET = '127.0.0.1:7233';
77
+
78
+ export function defaultConnectionOpts(): ConnectionOptionsWithDefaults {
79
+ return {
80
+ address: LOCAL_TARGET,
81
+ credentials: grpc.credentials.createInsecure(),
82
+ channelArgs: {},
83
+ interceptors: [makeGrpcRetryInterceptor(defaultGrpcRetryOptions())],
84
+ metadata: {},
85
+ connectTimeoutMs: 10_000,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * - Convert {@link ConnectionOptions.tls} to {@link grpc.ChannelCredentials}
91
+ * - Add the grpc.ssl_target_name_override GRPC {@link ConnectionOptions.channelArgs | channel arg}
92
+ * - Add default port to address if port not specified
93
+ */
94
+ function normalizeGRPCConfig(options?: ConnectionOptions): ConnectionOptions {
95
+ const { tls: tlsFromConfig, credentials, ...rest } = options || {};
96
+ if (rest.address) {
97
+ // eslint-disable-next-line prefer-const
98
+ let [host, port] = rest.address.split(':', 2);
99
+ port = port || '7233';
100
+ rest.address = `${host}:${port}`;
101
+ }
102
+ const tls = normalizeTlsConfig(tlsFromConfig);
103
+ if (tls) {
104
+ if (credentials) {
105
+ throw new TypeError('Both `tls` and `credentials` ConnectionOptions were provided');
106
+ }
107
+ return {
108
+ ...rest,
109
+ credentials: grpc.credentials.createSsl(
110
+ tls.serverRootCACertificate,
111
+ tls.clientCertPair?.key,
112
+ tls.clientCertPair?.crt
113
+ ),
114
+ channelArgs: {
115
+ ...rest.channelArgs,
116
+ ...(tls.serverNameOverride
117
+ ? {
118
+ 'grpc.ssl_target_name_override': tls.serverNameOverride,
119
+ 'grpc.default_authority': tls.serverNameOverride,
120
+ }
121
+ : undefined),
122
+ },
123
+ };
124
+ } else {
125
+ return rest;
126
+ }
127
+ }
128
+
129
+ export interface RPCImplOptions {
130
+ serviceName: string;
131
+ client: grpc.Client;
132
+ callContextStorage: AsyncLocalStorage<CallContext>;
133
+ interceptors?: grpc.Interceptor[];
134
+ }
135
+
136
+ export interface ConnectionCtorOptions {
137
+ readonly options: ConnectionOptionsWithDefaults;
138
+ readonly client: grpc.Client;
139
+ /**
140
+ * Raw gRPC access to the Temporal service.
141
+ *
142
+ * **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service.
143
+ */
144
+ readonly workflowService: WorkflowService;
145
+ readonly operatorService: OperatorService;
146
+ readonly callContextStorage: AsyncLocalStorage<CallContext>;
147
+ }
148
+
149
+ /**
150
+ * Client connection to the Temporal Server
151
+ *
152
+ * ⚠️ Connections are expensive to construct and should be reused. Make sure to {@link close} any unused connections to
153
+ * avoid leaking resources.
154
+ */
155
+ export class Connection {
156
+ /**
157
+ * @internal
158
+ */
159
+ public static readonly Client = grpc.makeGenericClientConstructor({}, 'WorkflowService', {});
160
+
161
+ public readonly options: ConnectionOptionsWithDefaults;
162
+ protected readonly client: grpc.Client;
163
+
164
+ /**
165
+ * Used to ensure `ensureConnected` is called once.
166
+ */
167
+ protected connectPromise?: Promise<void>;
168
+
169
+ /**
170
+ * Raw gRPC access to Temporal Server's {@link
171
+ * https://github.com/temporalio/api/blob/master/temporal/api/workflowservice/v1/service.proto | Workflow service}
172
+ */
173
+ public readonly workflowService: WorkflowService;
174
+
175
+ /**
176
+ * Raw gRPC access to Temporal Server's
177
+ * {@link https://github.com/temporalio/api/blob/master/temporal/api/operatorservice/v1/service.proto | Operator service}
178
+ */
179
+ public readonly operatorService: OperatorService;
180
+ readonly callContextStorage: AsyncLocalStorage<CallContext>;
181
+
182
+ protected static createCtorOptions(options?: ConnectionOptions): ConnectionCtorOptions {
183
+ const optionsWithDefaults = {
184
+ ...defaultConnectionOpts(),
185
+ ...filterNullAndUndefined(normalizeGRPCConfig(options)),
186
+ };
187
+ // Allow overriding this
188
+ optionsWithDefaults.metadata['client-name'] ??= 'temporal-typescript';
189
+ optionsWithDefaults.metadata['client-version'] ??= pkg.version;
190
+
191
+ const client = new this.Client(
192
+ optionsWithDefaults.address,
193
+ optionsWithDefaults.credentials,
194
+ optionsWithDefaults.channelArgs
195
+ );
196
+ const callContextStorage = new AsyncLocalStorage<CallContext>();
197
+ callContextStorage.enterWith({ metadata: optionsWithDefaults.metadata });
198
+
199
+ const workflowRpcImpl = this.generateRPCImplementation({
200
+ serviceName: 'temporal.api.workflowservice.v1.WorkflowService',
201
+ client,
202
+ callContextStorage,
203
+ interceptors: optionsWithDefaults?.interceptors,
204
+ });
205
+ const workflowService = WorkflowService.create(workflowRpcImpl, false, false);
206
+ const operatorRpcImpl = this.generateRPCImplementation({
207
+ serviceName: 'temporal.api.operatorservice.v1.OperatorService',
208
+ client,
209
+ callContextStorage,
210
+ interceptors: optionsWithDefaults?.interceptors,
211
+ });
212
+ const operatorService = OperatorService.create(operatorRpcImpl, false, false);
213
+
214
+ return {
215
+ client,
216
+ callContextStorage,
217
+ workflowService,
218
+ operatorService,
219
+ options: optionsWithDefaults,
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Ensure connection can be established.
225
+ *
226
+ * Does not need to be called if you use {@link connect}.
227
+ *
228
+ * This method's result is memoized to ensure it runs only once.
229
+ *
230
+ * Calls {@link proto.temporal.api.workflowservice.v1.WorkflowService.getSystemInfo} internally.
231
+ */
232
+ async ensureConnected(): Promise<void> {
233
+ if (this.connectPromise == null) {
234
+ const deadline = Date.now() + this.options.connectTimeoutMs;
235
+ this.connectPromise = (async () => {
236
+ await this.untilReady(deadline);
237
+
238
+ try {
239
+ await this.withDeadline(deadline, () => this.workflowService.getSystemInfo({}));
240
+ } catch (err) {
241
+ if (isServerErrorResponse(err)) {
242
+ // Ignore old servers
243
+ if (err.code !== grpc.status.UNIMPLEMENTED) {
244
+ throw new ServiceError('Failed to connect to Temporal server', { cause: err });
245
+ }
246
+ } else {
247
+ throw err;
248
+ }
249
+ }
250
+ })();
251
+ }
252
+ return this.connectPromise;
253
+ }
254
+
255
+ /**
256
+ * Create a lazy `Connection` instance.
257
+ *
258
+ * This method does not verify connectivity with the server. We recommend using {@link connect} instead.
259
+ */
260
+ static lazy(options?: ConnectionOptions): Connection {
261
+ return new this(this.createCtorOptions(options));
262
+ }
263
+
264
+ /**
265
+ * Establish a connection with the server and return a `Connection` instance.
266
+ *
267
+ * This is the preferred method of creating connections as it verifies connectivity by calling
268
+ * {@link ensureConnected}.
269
+ */
270
+ static async connect(options?: ConnectionOptions): Promise<Connection> {
271
+ const conn = this.lazy(options);
272
+ await conn.ensureConnected();
273
+ return conn;
274
+ }
275
+
276
+ protected constructor({
277
+ options,
278
+ client,
279
+ workflowService,
280
+ operatorService,
281
+ callContextStorage,
282
+ }: ConnectionCtorOptions) {
283
+ this.options = options;
284
+ this.client = client;
285
+ this.workflowService = workflowService;
286
+ this.operatorService = operatorService;
287
+ this.callContextStorage = callContextStorage;
288
+ }
289
+
290
+ protected static generateRPCImplementation({
291
+ serviceName,
292
+ client,
293
+ callContextStorage,
294
+ interceptors,
295
+ }: RPCImplOptions): RPCImpl {
296
+ return (method: { name: string }, requestData: any, callback: grpc.requestCallback<any>) => {
297
+ const metadataContainer = new grpc.Metadata();
298
+ const { metadata, deadline } = callContextStorage.getStore() ?? {};
299
+ if (metadata != null) {
300
+ for (const [k, v] of Object.entries(metadata)) {
301
+ metadataContainer.set(k, v);
302
+ }
303
+ }
304
+ return client.makeUnaryRequest(
305
+ `/${serviceName}/${method.name}`,
306
+ (arg: any) => arg,
307
+ (arg: any) => arg,
308
+ requestData,
309
+ metadataContainer,
310
+ { interceptors, deadline },
311
+ callback
312
+ );
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Set the deadline for any service requests executed in `fn`'s scope.
318
+ *
319
+ * @returns value returned from `fn`
320
+ */
321
+ async withDeadline<ReturnType>(deadline: number | Date, fn: () => Promise<ReturnType>): Promise<ReturnType> {
322
+ const cc = this.callContextStorage.getStore();
323
+ return await this.callContextStorage.run({ deadline, metadata: cc?.metadata }, fn);
324
+ }
325
+
326
+ /**
327
+ * Set metadata for any service requests executed in `fn`'s scope.
328
+ *
329
+ * The provided metadata is merged on top of any existing metadata in current scope, including metadata provided in
330
+ * {@link ConnectionOptions.metadata}.
331
+ *
332
+ * @returns value returned from `fn`
333
+ *
334
+ * @example
335
+ *
336
+ *```ts
337
+ *const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
338
+ * conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
339
+ *);
340
+ *```
341
+ */
342
+ async withMetadata<ReturnType>(metadata: Metadata, fn: () => Promise<ReturnType>): Promise<ReturnType> {
343
+ const cc = this.callContextStorage.getStore();
344
+ metadata = { ...cc?.metadata, ...metadata };
345
+ return await this.callContextStorage.run({ metadata, deadline: cc?.deadline }, fn);
346
+ }
347
+
348
+ /**
349
+ * Wait for successful connection to the server.
350
+ *
351
+ * @see https://grpc.github.io/grpc/node/grpc.Client.html#waitForReady__anchor
352
+ */
353
+ protected async untilReady(deadline: number): Promise<void> {
354
+ return new Promise<void>((resolve, reject) => {
355
+ this.client.waitForReady(deadline, (err) => {
356
+ if (err) {
357
+ reject(err);
358
+ } else {
359
+ resolve();
360
+ }
361
+ });
362
+ });
363
+ }
364
+
365
+ // This method is async for uniformity with NativeConnection which could be used in the future to power clients
366
+ /**
367
+ * Close the underlying gRPC client.
368
+ *
369
+ * Make sure to call this method to ensure proper resource cleanup.
370
+ */
371
+ public async close(): Promise<void> {
372
+ this.client.close();
373
+ }
374
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { ServerErrorResponse } from '@grpc/grpc-js';
2
+ import { RetryState, TemporalFailure } from '@temporalio/common';
3
+ export { WorkflowExecutionAlreadyStartedError } from '@temporalio/common';
4
+
5
+ /**
6
+ * Generic Error class for errors coming from the service
7
+ */
8
+ export class ServiceError extends Error {
9
+ public readonly name: string = 'ServiceError';
10
+ public readonly cause?: Error;
11
+
12
+ constructor(message: string, opts?: { cause: Error }) {
13
+ super(message);
14
+ this.cause = opts?.cause;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Thrown by the client while waiting on Workflow execution result if execution
20
+ * completes with failure.
21
+ *
22
+ * The failure type will be set in the `cause` attribute.
23
+ *
24
+ * For example if the workflow is cancelled, `cause` will be set to
25
+ * {@link CancelledFailure}.
26
+ */
27
+ export class WorkflowFailedError extends Error {
28
+ public readonly name: string = 'WorkflowFailedError';
29
+ public constructor(
30
+ message: string,
31
+ public readonly cause: TemporalFailure | undefined,
32
+ public readonly retryState: RetryState
33
+ ) {
34
+ super(message);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Thrown the by client while waiting on Workflow execution result if Workflow
40
+ * continues as new.
41
+ *
42
+ * Only thrown if asked not to follow the chain of execution (see {@link WorkflowOptions.followRuns}).
43
+ */
44
+ export class WorkflowContinuedAsNewError extends Error {
45
+ public readonly name: string = 'WorkflowExecutionContinuedAsNewError';
46
+ public constructor(message: string, public readonly newExecutionRunId: string) {
47
+ super(message);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Type assertion helper, assertion is mostly empty because any additional
53
+ * properties are optional.
54
+ */
55
+ export function isServerErrorResponse(err: unknown): err is ServerErrorResponse {
56
+ return err instanceof Error;
57
+ }