@temporalio/client 1.7.4 → 1.8.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/lib/async-completion-client.js +25 -22
- package/lib/async-completion-client.js.map +1 -1
- package/lib/build-id-types.d.ts +95 -0
- package/lib/build-id-types.js +28 -0
- package/lib/build-id-types.js.map +1 -0
- package/lib/client.d.ts +7 -0
- package/lib/client.js +6 -0
- package/lib/client.js.map +1 -1
- package/lib/connection.d.ts +2 -1
- package/lib/connection.js +1 -1
- package/lib/connection.js.map +1 -1
- package/lib/errors.d.ts +4 -4
- package/lib/errors.js +7 -7
- package/lib/errors.js.map +1 -1
- package/lib/helpers.d.ts +10 -0
- package/lib/helpers.js +31 -1
- package/lib/helpers.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/schedule-client.d.ts +1 -1
- package/lib/schedule-client.js +36 -27
- package/lib/schedule-client.js.map +1 -1
- package/lib/schedule-helpers.js +7 -7
- package/lib/schedule-helpers.js.map +1 -1
- package/lib/schedule-types.d.ts +5 -5
- package/lib/task-queue-client.d.ts +122 -0
- package/lib/task-queue-client.js +229 -0
- package/lib/task-queue-client.js.map +1 -0
- package/lib/workflow-client.d.ts +1 -1
- package/lib/workflow-client.js +93 -81
- package/lib/workflow-client.js.map +1 -1
- package/package.json +4 -4
- package/src/async-completion-client.ts +29 -23
- package/src/build-id-types.ts +146 -0
- package/src/client.ts +13 -0
- package/src/connection.ts +4 -4
- package/src/errors.ts +7 -6
- package/src/helpers.ts +36 -1
- package/src/index.ts +11 -0
- package/src/schedule-client.ts +35 -27
- package/src/schedule-helpers.ts +8 -7
- package/src/schedule-types.ts +5 -5
- package/src/task-queue-client.ts +297 -0
- package/src/workflow-client.ts +98 -84
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { status as grpcStatus } from '@grpc/grpc-js';
|
|
2
2
|
import { ensureTemporalFailure } from '@temporalio/common';
|
|
3
|
+
import type { temporal } from '@temporalio/proto';
|
|
3
4
|
import {
|
|
4
5
|
encodeErrorToFailure,
|
|
5
6
|
encodeToPayloads,
|
|
@@ -12,8 +13,9 @@ import {
|
|
|
12
13
|
LoadedWithDefaults,
|
|
13
14
|
WithDefaults,
|
|
14
15
|
} from './base-client';
|
|
15
|
-
import {
|
|
16
|
+
import { isGrpcServiceError } from './errors';
|
|
16
17
|
import { WorkflowService } from './types';
|
|
18
|
+
import { rethrowKnownErrorTypes } from './helpers';
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat an Activity that does not exist in the
|
|
@@ -95,10 +97,13 @@ export class AsyncCompletionClient extends BaseClient {
|
|
|
95
97
|
* Transforms grpc errors into well defined TS errors.
|
|
96
98
|
*/
|
|
97
99
|
protected handleError(err: unknown): never {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
+
if (isGrpcServiceError(err)) {
|
|
101
|
+
rethrowKnownErrorTypes(err);
|
|
102
|
+
|
|
103
|
+
if (err.code === grpcStatus.NOT_FOUND) {
|
|
100
104
|
throw new ActivityNotFoundError('Not found');
|
|
101
105
|
}
|
|
106
|
+
|
|
102
107
|
throw new ActivityCompletionError(err.details || err.message);
|
|
103
108
|
}
|
|
104
109
|
throw new ActivityCompletionError('Unexpected failure');
|
|
@@ -114,20 +119,21 @@ export class AsyncCompletionClient extends BaseClient {
|
|
|
114
119
|
async complete(fullActivityId: FullActivityId, result: unknown): Promise<void>;
|
|
115
120
|
|
|
116
121
|
async complete(taskTokenOrFullActivityId: Uint8Array | FullActivityId, result: unknown): Promise<void> {
|
|
122
|
+
const payloads = await encodeToPayloads(this.dataConverter, result);
|
|
117
123
|
try {
|
|
118
124
|
if (taskTokenOrFullActivityId instanceof Uint8Array) {
|
|
119
125
|
await this.workflowService.respondActivityTaskCompleted({
|
|
120
126
|
identity: this.options.identity,
|
|
121
127
|
namespace: this.options.namespace,
|
|
122
128
|
taskToken: taskTokenOrFullActivityId,
|
|
123
|
-
result: { payloads
|
|
129
|
+
result: { payloads },
|
|
124
130
|
});
|
|
125
131
|
} else {
|
|
126
132
|
await this.workflowService.respondActivityTaskCompletedById({
|
|
127
133
|
identity: this.options.identity,
|
|
128
134
|
namespace: this.options.namespace,
|
|
129
135
|
...taskTokenOrFullActivityId,
|
|
130
|
-
result: { payloads
|
|
136
|
+
result: { payloads },
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
} catch (err) {
|
|
@@ -145,20 +151,21 @@ export class AsyncCompletionClient extends BaseClient {
|
|
|
145
151
|
async fail(fullActivityId: FullActivityId, err: unknown): Promise<void>;
|
|
146
152
|
|
|
147
153
|
async fail(taskTokenOrFullActivityId: Uint8Array | FullActivityId, err: unknown): Promise<void> {
|
|
154
|
+
const failure = await encodeErrorToFailure(this.dataConverter, ensureTemporalFailure(err));
|
|
148
155
|
try {
|
|
149
156
|
if (taskTokenOrFullActivityId instanceof Uint8Array) {
|
|
150
157
|
await this.workflowService.respondActivityTaskFailed({
|
|
151
158
|
identity: this.options.identity,
|
|
152
159
|
namespace: this.options.namespace,
|
|
153
160
|
taskToken: taskTokenOrFullActivityId,
|
|
154
|
-
failure
|
|
161
|
+
failure,
|
|
155
162
|
});
|
|
156
163
|
} else {
|
|
157
164
|
await this.workflowService.respondActivityTaskFailedById({
|
|
158
165
|
identity: this.options.identity,
|
|
159
166
|
namespace: this.options.namespace,
|
|
160
167
|
...taskTokenOrFullActivityId,
|
|
161
|
-
failure
|
|
168
|
+
failure,
|
|
162
169
|
});
|
|
163
170
|
}
|
|
164
171
|
} catch (err) {
|
|
@@ -176,20 +183,21 @@ export class AsyncCompletionClient extends BaseClient {
|
|
|
176
183
|
reportCancellation(fullActivityId: FullActivityId, details?: unknown): Promise<void>;
|
|
177
184
|
|
|
178
185
|
async reportCancellation(taskTokenOrFullActivityId: Uint8Array | FullActivityId, details?: unknown): Promise<void> {
|
|
186
|
+
const payloads = await encodeToPayloads(this.dataConverter, details);
|
|
179
187
|
try {
|
|
180
188
|
if (taskTokenOrFullActivityId instanceof Uint8Array) {
|
|
181
189
|
await this.workflowService.respondActivityTaskCanceled({
|
|
182
190
|
identity: this.options.identity,
|
|
183
191
|
namespace: this.options.namespace,
|
|
184
192
|
taskToken: taskTokenOrFullActivityId,
|
|
185
|
-
details: { payloads
|
|
193
|
+
details: { payloads },
|
|
186
194
|
});
|
|
187
195
|
} else {
|
|
188
196
|
await this.workflowService.respondActivityTaskCanceledById({
|
|
189
197
|
identity: this.options.identity,
|
|
190
198
|
namespace: this.options.namespace,
|
|
191
199
|
...taskTokenOrFullActivityId,
|
|
192
|
-
details: { payloads
|
|
200
|
+
details: { payloads },
|
|
193
201
|
});
|
|
194
202
|
}
|
|
195
203
|
} catch (err) {
|
|
@@ -207,33 +215,31 @@ export class AsyncCompletionClient extends BaseClient {
|
|
|
207
215
|
heartbeat(fullActivityId: FullActivityId, details?: unknown): Promise<void>;
|
|
208
216
|
|
|
209
217
|
async heartbeat(taskTokenOrFullActivityId: Uint8Array | FullActivityId, details?: unknown): Promise<void> {
|
|
218
|
+
const payloads = await encodeToPayloads(this.dataConverter, details);
|
|
219
|
+
let cancelRequested = false;
|
|
210
220
|
try {
|
|
221
|
+
let response: temporal.api.workflowservice.v1.RecordActivityTaskHeartbeatResponse;
|
|
211
222
|
if (taskTokenOrFullActivityId instanceof Uint8Array) {
|
|
212
|
-
|
|
223
|
+
response = await this.workflowService.recordActivityTaskHeartbeat({
|
|
213
224
|
identity: this.options.identity,
|
|
214
225
|
namespace: this.options.namespace,
|
|
215
226
|
taskToken: taskTokenOrFullActivityId,
|
|
216
|
-
details: { payloads
|
|
227
|
+
details: { payloads },
|
|
217
228
|
});
|
|
218
|
-
if (cancelRequested) {
|
|
219
|
-
throw new ActivityCancelledError('cancelled');
|
|
220
|
-
}
|
|
221
229
|
} else {
|
|
222
|
-
|
|
230
|
+
response = await this.workflowService.recordActivityTaskHeartbeatById({
|
|
223
231
|
identity: this.options.identity,
|
|
224
232
|
namespace: this.options.namespace,
|
|
225
233
|
...taskTokenOrFullActivityId,
|
|
226
|
-
details: { payloads
|
|
234
|
+
details: { payloads },
|
|
227
235
|
});
|
|
228
|
-
if (cancelRequested) {
|
|
229
|
-
throw new ActivityCancelledError('cancelled');
|
|
230
|
-
}
|
|
231
236
|
}
|
|
237
|
+
cancelRequested = !!response.cancelRequested;
|
|
232
238
|
} catch (err) {
|
|
233
|
-
if (err instanceof ActivityCancelledError) {
|
|
234
|
-
throw err;
|
|
235
|
-
}
|
|
236
239
|
this.handleError(err);
|
|
237
240
|
}
|
|
241
|
+
if (cancelRequested) {
|
|
242
|
+
throw new ActivityCancelledError('cancelled');
|
|
243
|
+
}
|
|
238
244
|
}
|
|
239
245
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { temporal } from '@temporalio/proto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Operations that can be passed to {@link TaskQueueClient.updateBuildIdCompatibility}.
|
|
5
|
+
*
|
|
6
|
+
* @experimental
|
|
7
|
+
*/
|
|
8
|
+
export type BuildIdOperation =
|
|
9
|
+
| AddNewIdInNewDefaultSet
|
|
10
|
+
| AddNewCompatibleVersion
|
|
11
|
+
| PromoteSetByBuildId
|
|
12
|
+
| PromoteBuildIdWithinSet
|
|
13
|
+
| MergeSets;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Adds a new Build Id into a new set, which will be used as the default set for
|
|
17
|
+
* the queue. This means all new workflows will start on this Build Id.
|
|
18
|
+
*
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
export interface AddNewIdInNewDefaultSet {
|
|
22
|
+
operation: 'addNewIdInNewDefaultSet';
|
|
23
|
+
buildId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Adds a new Build Id into an existing compatible set. The newly added ID becomes
|
|
28
|
+
* the default for that compatible set, and thus new workflow tasks for workflows which have been
|
|
29
|
+
* executing on workers in that set will now start on this new Build Id.
|
|
30
|
+
*
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
export interface AddNewCompatibleVersion {
|
|
34
|
+
operation: 'addNewCompatibleVersion';
|
|
35
|
+
// The Build Id to add to an existing compatible set.
|
|
36
|
+
buildId: string;
|
|
37
|
+
// A Build Id which must already be defined on the task queue, and is used to
|
|
38
|
+
// find the compatible set to add the new id to.
|
|
39
|
+
existingCompatibleBuildId: string;
|
|
40
|
+
// If set to true, the targeted set will also be promoted to become the
|
|
41
|
+
// overall default set for the queue.
|
|
42
|
+
promoteSet?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Promotes a set of compatible Build Ids to become the current
|
|
47
|
+
* default set for the task queue. Any Build Id in the set may be used to
|
|
48
|
+
* target it.
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
export interface PromoteSetByBuildId {
|
|
53
|
+
operation: 'promoteSetByBuildId';
|
|
54
|
+
buildId: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Promotes a Build Id within an existing set to become the default ID for that
|
|
59
|
+
* set.
|
|
60
|
+
*
|
|
61
|
+
* @experimental
|
|
62
|
+
*/
|
|
63
|
+
export interface PromoteBuildIdWithinSet {
|
|
64
|
+
operation: 'promoteBuildIdWithinSet';
|
|
65
|
+
buildId: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Merges two sets into one set, thus declaring all the Build Ids in both as
|
|
70
|
+
* compatible with one another. The default of the primary set is maintained as
|
|
71
|
+
* the merged set's overall default.
|
|
72
|
+
*
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
75
|
+
export interface MergeSets {
|
|
76
|
+
operation: 'mergeSets';
|
|
77
|
+
// A Build Id which is used to find the primary set to be merged.
|
|
78
|
+
primaryBuildId: string;
|
|
79
|
+
// A Build Id which is used to find the secondary set to be merged.
|
|
80
|
+
secondaryBuildId: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Represents the sets of compatible Build Id versions associated with some
|
|
85
|
+
* Task Queue, as fetched by {@link TaskQueueClient.getBuildIdCompatability}.
|
|
86
|
+
*
|
|
87
|
+
* @experimental
|
|
88
|
+
*/
|
|
89
|
+
export interface WorkerBuildIdVersionSets {
|
|
90
|
+
/**
|
|
91
|
+
* All version sets that were fetched for this task queue.
|
|
92
|
+
*/
|
|
93
|
+
readonly versionSets: BuildIdVersionSet[];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the default set of compatible Build Ids for the task queue these sets are
|
|
97
|
+
* associated with.
|
|
98
|
+
*/
|
|
99
|
+
defaultSet: BuildIdVersionSet;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Returns the overall default Build Id for the task queue these sets are
|
|
103
|
+
* associated with.
|
|
104
|
+
*/
|
|
105
|
+
defaultBuildId: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Represents one set of compatible Build Ids.
|
|
110
|
+
*
|
|
111
|
+
* @experimental
|
|
112
|
+
*/
|
|
113
|
+
export interface BuildIdVersionSet {
|
|
114
|
+
// All build IDs contained in the set.
|
|
115
|
+
readonly buildIds: string[];
|
|
116
|
+
|
|
117
|
+
// Returns the default Build Id for this set
|
|
118
|
+
readonly default: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function versionSetsFromProto(
|
|
122
|
+
resp: temporal.api.workflowservice.v1.GetWorkerBuildIdCompatibilityResponse
|
|
123
|
+
): WorkerBuildIdVersionSets {
|
|
124
|
+
if (resp == null || resp.majorVersionSets == null || resp.majorVersionSets.length === 0) {
|
|
125
|
+
throw new Error('Must be constructed from a compatability response with at least one version set');
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
versionSets: resp.majorVersionSets.map((set) => versionSetFromProto(set)),
|
|
129
|
+
get defaultSet(): BuildIdVersionSet {
|
|
130
|
+
return this.versionSets[this.versionSets.length - 1];
|
|
131
|
+
},
|
|
132
|
+
get defaultBuildId(): string {
|
|
133
|
+
return this.defaultSet.default;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function versionSetFromProto(set: temporal.api.taskqueue.v1.ICompatibleVersionSet): BuildIdVersionSet {
|
|
139
|
+
if (set == null || set.buildIds == null || set.buildIds.length === 0) {
|
|
140
|
+
throw new Error('Compatible version sets must contain at least one Build Id');
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
buildIds: set.buildIds,
|
|
144
|
+
default: set.buildIds[set.buildIds.length - 1],
|
|
145
|
+
};
|
|
146
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { ClientInterceptors } from './interceptors';
|
|
|
6
6
|
import { ScheduleClient } from './schedule-client';
|
|
7
7
|
import { WorkflowService } from './types';
|
|
8
8
|
import { WorkflowClient } from './workflow-client';
|
|
9
|
+
import { TaskQueueClient } from './task-queue-client';
|
|
9
10
|
|
|
10
11
|
export interface ClientOptions extends BaseClientOptions {
|
|
11
12
|
/**
|
|
@@ -46,6 +47,12 @@ export class Client extends BaseClient {
|
|
|
46
47
|
* @experimental
|
|
47
48
|
*/
|
|
48
49
|
public readonly schedule: ScheduleClient;
|
|
50
|
+
/**
|
|
51
|
+
* Task Queue sub-client - use to perform operations on Task Queues
|
|
52
|
+
*
|
|
53
|
+
* @experimental
|
|
54
|
+
*/
|
|
55
|
+
public readonly taskQueue: TaskQueueClient;
|
|
49
56
|
|
|
50
57
|
constructor(options?: ClientOptions) {
|
|
51
58
|
super(options);
|
|
@@ -73,6 +80,12 @@ export class Client extends BaseClient {
|
|
|
73
80
|
interceptors: interceptors?.schedule,
|
|
74
81
|
});
|
|
75
82
|
|
|
83
|
+
this.taskQueue = new TaskQueueClient({
|
|
84
|
+
...commonOptions,
|
|
85
|
+
connection: this.connection,
|
|
86
|
+
dataConverter: this.dataConverter,
|
|
87
|
+
});
|
|
88
|
+
|
|
76
89
|
this.options = {
|
|
77
90
|
...defaultBaseClientOptions(),
|
|
78
91
|
...filterNullAndUndefined(commonOptions),
|
package/src/connection.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
2
2
|
import * as grpc from '@grpc/grpc-js';
|
|
3
3
|
import type { RPCImpl } from 'protobufjs';
|
|
4
4
|
import { filterNullAndUndefined, normalizeTlsConfig, TLSConfig } from '@temporalio/common/lib/internal-non-workflow';
|
|
5
|
-
import { msOptionalToNumber } from '@temporalio/common/lib/time';
|
|
6
|
-
import {
|
|
5
|
+
import { Duration, msOptionalToNumber } from '@temporalio/common/lib/time';
|
|
6
|
+
import { isGrpcServiceError, ServiceError } from './errors';
|
|
7
7
|
import { defaultGrpcRetryOptions, makeGrpcRetryInterceptor } from './grpc-retry';
|
|
8
8
|
import pkg from './pkg';
|
|
9
9
|
import { CallContext, HealthService, Metadata, OperatorService, WorkflowService } from './types';
|
|
@@ -81,7 +81,7 @@ export interface ConnectionOptions {
|
|
|
81
81
|
* @format number of milliseconds or {@link https://www.npmjs.com/package/ms | ms-formatted string}
|
|
82
82
|
* @default 10 seconds
|
|
83
83
|
*/
|
|
84
|
-
connectTimeout?:
|
|
84
|
+
connectTimeout?: Duration;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export type ConnectionOptionsWithDefaults = Required<Omit<ConnectionOptions, 'tls' | 'connectTimeout'>> & {
|
|
@@ -276,7 +276,7 @@ export class Connection {
|
|
|
276
276
|
try {
|
|
277
277
|
await this.withDeadline(deadline, () => this.workflowService.getSystemInfo({}));
|
|
278
278
|
} catch (err) {
|
|
279
|
-
if (
|
|
279
|
+
if (isGrpcServiceError(err)) {
|
|
280
280
|
// Ignore old servers
|
|
281
281
|
if (err.code !== grpc.status.UNIMPLEMENTED) {
|
|
282
282
|
throw new ServiceError('Failed to connect to Temporal server', { cause: err });
|
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ServiceError as GrpcServiceError } from '@grpc/grpc-js';
|
|
2
2
|
import { RetryState, TemporalFailure } from '@temporalio/common';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -47,10 +47,11 @@ export class WorkflowContinuedAsNewError extends Error {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export function isGrpcServiceError(err: unknown): err is GrpcServiceError {
|
|
51
|
+
return err instanceof Error && (err as any).details !== undefined && (err as any).metadata !== undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
/**
|
|
51
|
-
*
|
|
52
|
-
* properties are optional.
|
|
55
|
+
* @deprecated Use `isGrpcServiceError` instead
|
|
53
56
|
*/
|
|
54
|
-
export
|
|
55
|
-
return err instanceof Error;
|
|
56
|
-
}
|
|
57
|
+
export const isServerErrorResponse = isGrpcServiceError;
|
package/src/helpers.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { ServiceError as GrpcServiceError } from '@grpc/grpc-js';
|
|
1
2
|
import {
|
|
2
3
|
LoadedDataConverter,
|
|
3
4
|
mapFromPayloads,
|
|
5
|
+
NamespaceNotFoundError,
|
|
4
6
|
searchAttributePayloadConverter,
|
|
5
7
|
SearchAttributes,
|
|
6
8
|
} from '@temporalio/common';
|
|
7
9
|
import { Replace } from '@temporalio/common/lib/type-helpers';
|
|
8
10
|
import { optionalTsToDate, tsToDate } from '@temporalio/common/lib/time';
|
|
9
11
|
import { decodeMapFromPayloads } from '@temporalio/common/lib/internal-non-workflow/codec-helpers';
|
|
10
|
-
import { temporal } from '@temporalio/proto';
|
|
12
|
+
import { temporal, google } from '@temporalio/proto';
|
|
11
13
|
import { RawWorkflowExecutionInfo, WorkflowExecutionInfo, WorkflowExecutionStatusName } from './types';
|
|
12
14
|
|
|
13
15
|
function workflowStatusCodeToName(code: temporal.api.enums.v1.WorkflowExecutionStatus): WorkflowExecutionStatusName {
|
|
@@ -75,3 +77,36 @@ export async function executionInfoFromRaw<T>(
|
|
|
75
77
|
raw: rawDataToEmbed,
|
|
76
78
|
};
|
|
77
79
|
}
|
|
80
|
+
|
|
81
|
+
type ErrorDetailsName = `temporal.api.errordetails.v1.${keyof typeof temporal.api.errordetails.v1}`;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* If the error type can be determined based on embedded grpc error details,
|
|
85
|
+
* then rethrow the appropriate TypeScript error. Otherwise do nothing.
|
|
86
|
+
*
|
|
87
|
+
* This function should be used before falling back to generic error handling
|
|
88
|
+
* based on grpc error code. Very few error types are currently supported, but
|
|
89
|
+
* this function will be expanded over time as more server error types are added.
|
|
90
|
+
*/
|
|
91
|
+
export function rethrowKnownErrorTypes(err: GrpcServiceError): void {
|
|
92
|
+
// We really don't expect multiple error details, but this really is an array, so just in case...
|
|
93
|
+
for (const entry of getGrpcStatusDetails(err) ?? []) {
|
|
94
|
+
if (!entry.type_url || !entry.value) continue;
|
|
95
|
+
const type = entry.type_url.replace(/^type.googleapis.com\//, '') as ErrorDetailsName;
|
|
96
|
+
|
|
97
|
+
switch (type) {
|
|
98
|
+
case 'temporal.api.errordetails.v1.NamespaceNotFoundFailure': {
|
|
99
|
+
const { namespace } = temporal.api.errordetails.v1.NamespaceNotFoundFailure.decode(entry.value);
|
|
100
|
+
throw new NamespaceNotFoundError(namespace);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getGrpcStatusDetails(err: GrpcServiceError): google.rpc.Status['details'] | undefined {
|
|
107
|
+
const statusBuffer = err.metadata.get('grpc-status-details-bin')?.[0];
|
|
108
|
+
if (!statusBuffer || typeof statusBuffer === 'string') {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return google.rpc.Status.decode(statusBuffer).details;
|
|
112
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -38,3 +38,14 @@ export * from './workflow-client';
|
|
|
38
38
|
export * from './workflow-options';
|
|
39
39
|
export * from './schedule-types';
|
|
40
40
|
export * from './schedule-client';
|
|
41
|
+
export * from './task-queue-client';
|
|
42
|
+
export {
|
|
43
|
+
WorkerBuildIdVersionSets,
|
|
44
|
+
BuildIdVersionSet,
|
|
45
|
+
BuildIdOperation,
|
|
46
|
+
PromoteSetByBuildId,
|
|
47
|
+
PromoteBuildIdWithinSet,
|
|
48
|
+
MergeSets,
|
|
49
|
+
AddNewIdInNewDefaultSet,
|
|
50
|
+
AddNewCompatibleVersion,
|
|
51
|
+
} from './build-id-types';
|
package/src/schedule-client.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { temporal } from '@temporalio/proto';
|
|
|
11
11
|
import { optionalDateToTs, optionalTsToDate, optionalTsToMs, tsToDate } from '@temporalio/common/lib/time';
|
|
12
12
|
import { CreateScheduleInput, CreateScheduleOutput, ScheduleClientInterceptor } from './interceptors';
|
|
13
13
|
import { WorkflowService } from './types';
|
|
14
|
-
import {
|
|
14
|
+
import { isGrpcServiceError, ServiceError } from './errors';
|
|
15
15
|
import {
|
|
16
16
|
Backfill,
|
|
17
17
|
CompiledScheduleUpdateOptions,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
LoadedWithDefaults,
|
|
46
46
|
WithDefaults,
|
|
47
47
|
} from './base-client';
|
|
48
|
+
import { rethrowKnownErrorTypes } from './helpers';
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Handle to a single Schedule
|
|
@@ -263,7 +264,7 @@ export class ScheduleClient extends BaseClient {
|
|
|
263
264
|
if (err.code === grpcStatus.ALREADY_EXISTS) {
|
|
264
265
|
throw new ScheduleAlreadyRunning('Schedule already exists and is running', opts.scheduleId);
|
|
265
266
|
}
|
|
266
|
-
this.rethrowGrpcError(err,
|
|
267
|
+
this.rethrowGrpcError(err, 'Failed to create schedule', opts.scheduleId);
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -279,7 +280,7 @@ export class ScheduleClient extends BaseClient {
|
|
|
279
280
|
scheduleId,
|
|
280
281
|
});
|
|
281
282
|
} catch (err: any) {
|
|
282
|
-
this.rethrowGrpcError(err,
|
|
283
|
+
this.rethrowGrpcError(err, 'Failed to describe schedule', scheduleId);
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
|
|
@@ -291,21 +292,22 @@ export class ScheduleClient extends BaseClient {
|
|
|
291
292
|
opts: CompiledScheduleUpdateOptions,
|
|
292
293
|
header: Headers
|
|
293
294
|
): Promise<temporal.api.workflowservice.v1.IUpdateScheduleResponse> {
|
|
295
|
+
const req = {
|
|
296
|
+
namespace: this.options.namespace,
|
|
297
|
+
scheduleId,
|
|
298
|
+
schedule: {
|
|
299
|
+
spec: encodeScheduleSpec(opts.spec),
|
|
300
|
+
action: await encodeScheduleAction(this.dataConverter, opts.action, header),
|
|
301
|
+
policies: encodeSchedulePolicies(opts.policies),
|
|
302
|
+
state: encodeScheduleState(opts.state),
|
|
303
|
+
},
|
|
304
|
+
identity: this.options.identity,
|
|
305
|
+
requestId: uuid4(),
|
|
306
|
+
};
|
|
294
307
|
try {
|
|
295
|
-
return await this.workflowService.updateSchedule(
|
|
296
|
-
namespace: this.options.namespace,
|
|
297
|
-
scheduleId,
|
|
298
|
-
schedule: {
|
|
299
|
-
spec: encodeScheduleSpec(opts.spec),
|
|
300
|
-
action: await encodeScheduleAction(this.dataConverter, opts.action, header),
|
|
301
|
-
policies: encodeSchedulePolicies(opts.policies),
|
|
302
|
-
state: encodeScheduleState(opts.state),
|
|
303
|
-
},
|
|
304
|
-
identity: this.options.identity,
|
|
305
|
-
requestId: uuid4(),
|
|
306
|
-
});
|
|
308
|
+
return await this.workflowService.updateSchedule(req);
|
|
307
309
|
} catch (err: any) {
|
|
308
|
-
this.rethrowGrpcError(err,
|
|
310
|
+
this.rethrowGrpcError(err, 'Failed to update schedule', scheduleId);
|
|
309
311
|
}
|
|
310
312
|
}
|
|
311
313
|
|
|
@@ -325,7 +327,7 @@ export class ScheduleClient extends BaseClient {
|
|
|
325
327
|
patch,
|
|
326
328
|
});
|
|
327
329
|
} catch (err: any) {
|
|
328
|
-
this.rethrowGrpcError(err,
|
|
330
|
+
this.rethrowGrpcError(err, 'Failed to patch schedule', scheduleId);
|
|
329
331
|
}
|
|
330
332
|
}
|
|
331
333
|
|
|
@@ -342,7 +344,7 @@ export class ScheduleClient extends BaseClient {
|
|
|
342
344
|
scheduleId,
|
|
343
345
|
});
|
|
344
346
|
} catch (err: any) {
|
|
345
|
-
this.rethrowGrpcError(err,
|
|
347
|
+
this.rethrowGrpcError(err, 'Failed to delete schedule', scheduleId);
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
350
|
|
|
@@ -365,20 +367,23 @@ export class ScheduleClient extends BaseClient {
|
|
|
365
367
|
public async *list(options?: ListScheduleOptions): AsyncIterable<ScheduleSummary> {
|
|
366
368
|
let nextPageToken: Uint8Array | undefined = undefined;
|
|
367
369
|
for (;;) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
+
let response: temporal.api.workflowservice.v1.ListSchedulesResponse;
|
|
371
|
+
try {
|
|
372
|
+
response = await this.workflowService.listSchedules({
|
|
370
373
|
nextPageToken,
|
|
371
374
|
namespace: this.options.namespace,
|
|
372
375
|
maximumPageSize: options?.pageSize,
|
|
373
|
-
}
|
|
374
|
-
)
|
|
376
|
+
});
|
|
377
|
+
} catch (e) {
|
|
378
|
+
this.rethrowGrpcError(e, 'Failed to list schedules', undefined);
|
|
379
|
+
}
|
|
375
380
|
|
|
376
381
|
for (const raw of response.schedules ?? []) {
|
|
377
382
|
yield <ScheduleSummary>{
|
|
378
383
|
scheduleId: raw.scheduleId,
|
|
379
384
|
|
|
380
385
|
spec: decodeScheduleSpec(raw.info?.spec ?? {}),
|
|
381
|
-
action: raw.info?.workflowType
|
|
386
|
+
action: raw.info?.workflowType && {
|
|
382
387
|
type: 'startWorkflow',
|
|
383
388
|
workflowType: raw.info.workflowType.name,
|
|
384
389
|
},
|
|
@@ -496,10 +501,12 @@ export class ScheduleClient extends BaseClient {
|
|
|
496
501
|
};
|
|
497
502
|
}
|
|
498
503
|
|
|
499
|
-
protected rethrowGrpcError(err: unknown,
|
|
500
|
-
if (
|
|
504
|
+
protected rethrowGrpcError(err: unknown, fallbackMessage: string, scheduleId?: string): never {
|
|
505
|
+
if (isGrpcServiceError(err)) {
|
|
506
|
+
rethrowKnownErrorTypes(err);
|
|
507
|
+
|
|
501
508
|
if (err.code === grpcStatus.NOT_FOUND) {
|
|
502
|
-
throw new ScheduleNotFoundError(err.details ?? 'Schedule not found', scheduleId);
|
|
509
|
+
throw new ScheduleNotFoundError(err.details ?? 'Schedule not found', scheduleId ?? '');
|
|
503
510
|
}
|
|
504
511
|
if (
|
|
505
512
|
err.code === grpcStatus.INVALID_ARGUMENT &&
|
|
@@ -507,9 +514,10 @@ export class ScheduleClient extends BaseClient {
|
|
|
507
514
|
) {
|
|
508
515
|
throw new TypeError(err.message.replace(/^3 INVALID_ARGUMENT: Invalid schedule spec: /, ''));
|
|
509
516
|
}
|
|
517
|
+
|
|
510
518
|
throw new ServiceError(fallbackMessage, { cause: err });
|
|
511
519
|
}
|
|
512
|
-
throw new ServiceError('Unexpected error while making gRPC request');
|
|
520
|
+
throw new ServiceError('Unexpected error while making gRPC request', { cause: err as Error });
|
|
513
521
|
}
|
|
514
522
|
}
|
|
515
523
|
|
package/src/schedule-helpers.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Long from 'long'; // eslint-disable-line import/no-named-as-default
|
|
|
2
2
|
import {
|
|
3
3
|
compileRetryPolicy,
|
|
4
4
|
decompileRetryPolicy,
|
|
5
|
+
extractWorkflowType,
|
|
5
6
|
LoadedDataConverter,
|
|
6
7
|
mapFromPayloads,
|
|
7
8
|
mapToPayloads,
|
|
@@ -66,7 +67,7 @@ const [encodeMinute, decodeMinue] = makeCalendarSpecFieldCoders(
|
|
|
66
67
|
|
|
67
68
|
const [encodeHour, decodeHour] = makeCalendarSpecFieldCoders(
|
|
68
69
|
'hour',
|
|
69
|
-
(x: number) => (typeof x === 'number' && x >= 0 && x <=
|
|
70
|
+
(x: number) => (typeof x === 'number' && x >= 0 && x <= 23 ? x : undefined),
|
|
70
71
|
(x: number) => x,
|
|
71
72
|
[{ start: 0, end: 0, step: 0 }], // default to 0
|
|
72
73
|
[{ start: 0, end: 23, step: 1 }]
|
|
@@ -74,7 +75,7 @@ const [encodeHour, decodeHour] = makeCalendarSpecFieldCoders(
|
|
|
74
75
|
|
|
75
76
|
const [encodeDayOfMonth, decodeDayOfMonth] = makeCalendarSpecFieldCoders(
|
|
76
77
|
'dayOfMonth',
|
|
77
|
-
(x: number) => (typeof x === 'number' && x >= 0 && x <=
|
|
78
|
+
(x: number) => (typeof x === 'number' && x >= 0 && x <= 31 ? x : undefined),
|
|
78
79
|
(x: number) => x,
|
|
79
80
|
[{ start: 1, end: 31, step: 1 }], // default to *
|
|
80
81
|
[{ start: 1, end: 31, step: 1 }]
|
|
@@ -139,7 +140,7 @@ function makeCalendarSpecFieldCoders<Unit>(
|
|
|
139
140
|
const value = encodeValueFn(item as Unit);
|
|
140
141
|
if (value !== undefined) return { start: value, end: value, step: 1 };
|
|
141
142
|
}
|
|
142
|
-
throw new
|
|
143
|
+
throw new TypeError(`Invalid CalendarSpec component for field ${fieldName}: '${item}' of type '${typeof item}'`);
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -208,7 +209,7 @@ export function decodeOverlapPolicy(input?: temporal.api.enums.v1.ScheduleOverla
|
|
|
208
209
|
|
|
209
210
|
export function compileScheduleOptions(options: ScheduleOptions): CompiledScheduleOptions {
|
|
210
211
|
const workflowTypeOrFunc = options.action.workflowType;
|
|
211
|
-
const workflowType =
|
|
212
|
+
const workflowType = extractWorkflowType(workflowTypeOrFunc);
|
|
212
213
|
return {
|
|
213
214
|
...options,
|
|
214
215
|
action: {
|
|
@@ -222,7 +223,7 @@ export function compileScheduleOptions(options: ScheduleOptions): CompiledSchedu
|
|
|
222
223
|
|
|
223
224
|
export function compileUpdatedScheduleOptions(options: ScheduleUpdateOptions): CompiledScheduleUpdateOptions {
|
|
224
225
|
const workflowTypeOrFunc = options.action.workflowType;
|
|
225
|
-
const workflowType =
|
|
226
|
+
const workflowType = extractWorkflowType(workflowTypeOrFunc);
|
|
226
227
|
return {
|
|
227
228
|
...options,
|
|
228
229
|
action: {
|
|
@@ -349,7 +350,7 @@ export async function decodeScheduleAction(
|
|
|
349
350
|
workflowTaskTimeout: optionalTsToMs(pb.startWorkflow.workflowTaskTimeout),
|
|
350
351
|
};
|
|
351
352
|
}
|
|
352
|
-
throw new
|
|
353
|
+
throw new TypeError('Unsupported schedule action');
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
export function decodeSearchAttributes(
|
|
@@ -397,7 +398,7 @@ export function decodeScheduleRecentActions(
|
|
|
397
398
|
firstExecutionRunId: executionResult.startWorkflowResult!.runId!,
|
|
398
399
|
},
|
|
399
400
|
};
|
|
400
|
-
} else throw new
|
|
401
|
+
} else throw new TypeError('Unsupported schedule action');
|
|
401
402
|
|
|
402
403
|
return {
|
|
403
404
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|