@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.
Files changed (45) hide show
  1. package/lib/async-completion-client.js +25 -22
  2. package/lib/async-completion-client.js.map +1 -1
  3. package/lib/build-id-types.d.ts +95 -0
  4. package/lib/build-id-types.js +28 -0
  5. package/lib/build-id-types.js.map +1 -0
  6. package/lib/client.d.ts +7 -0
  7. package/lib/client.js +6 -0
  8. package/lib/client.js.map +1 -1
  9. package/lib/connection.d.ts +2 -1
  10. package/lib/connection.js +1 -1
  11. package/lib/connection.js.map +1 -1
  12. package/lib/errors.d.ts +4 -4
  13. package/lib/errors.js +7 -7
  14. package/lib/errors.js.map +1 -1
  15. package/lib/helpers.d.ts +10 -0
  16. package/lib/helpers.js +31 -1
  17. package/lib/helpers.js.map +1 -1
  18. package/lib/index.d.ts +2 -0
  19. package/lib/index.js +1 -0
  20. package/lib/index.js.map +1 -1
  21. package/lib/schedule-client.d.ts +1 -1
  22. package/lib/schedule-client.js +36 -27
  23. package/lib/schedule-client.js.map +1 -1
  24. package/lib/schedule-helpers.js +7 -7
  25. package/lib/schedule-helpers.js.map +1 -1
  26. package/lib/schedule-types.d.ts +5 -5
  27. package/lib/task-queue-client.d.ts +122 -0
  28. package/lib/task-queue-client.js +229 -0
  29. package/lib/task-queue-client.js.map +1 -0
  30. package/lib/workflow-client.d.ts +1 -1
  31. package/lib/workflow-client.js +93 -81
  32. package/lib/workflow-client.js.map +1 -1
  33. package/package.json +4 -4
  34. package/src/async-completion-client.ts +29 -23
  35. package/src/build-id-types.ts +146 -0
  36. package/src/client.ts +13 -0
  37. package/src/connection.ts +4 -4
  38. package/src/errors.ts +7 -6
  39. package/src/helpers.ts +36 -1
  40. package/src/index.ts +11 -0
  41. package/src/schedule-client.ts +35 -27
  42. package/src/schedule-helpers.ts +8 -7
  43. package/src/schedule-types.ts +5 -5
  44. package/src/task-queue-client.ts +297 -0
  45. package/src/workflow-client.ts +98 -84
@@ -1,5 +1,6 @@
1
- import { Status } from '@grpc/grpc-js/build/src/constants';
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 { isServerErrorResponse } from './errors';
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 (isServerErrorResponse(err)) {
99
- if (err.code === Status.NOT_FOUND) {
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: await encodeToPayloads(this.dataConverter, result) },
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: await encodeToPayloads(this.dataConverter, result) },
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: await encodeErrorToFailure(this.dataConverter, ensureTemporalFailure(err)),
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: await encodeErrorToFailure(this.dataConverter, err),
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: await encodeToPayloads(this.dataConverter, details) },
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: await encodeToPayloads(this.dataConverter, details) },
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
- const { cancelRequested } = await this.workflowService.recordActivityTaskHeartbeat({
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: await encodeToPayloads(this.dataConverter, details) },
227
+ details: { payloads },
217
228
  });
218
- if (cancelRequested) {
219
- throw new ActivityCancelledError('cancelled');
220
- }
221
229
  } else {
222
- const { cancelRequested } = await this.workflowService.recordActivityTaskHeartbeatById({
230
+ response = await this.workflowService.recordActivityTaskHeartbeatById({
223
231
  identity: this.options.identity,
224
232
  namespace: this.options.namespace,
225
233
  ...taskTokenOrFullActivityId,
226
- details: { payloads: await encodeToPayloads(this.dataConverter, details) },
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 { isServerErrorResponse, ServiceError } from './errors';
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?: number | string;
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 (isServerErrorResponse(err)) {
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 { ServerErrorResponse } from '@grpc/grpc-js';
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
- * Type assertion helper, assertion is mostly empty because any additional
52
- * properties are optional.
55
+ * @deprecated Use `isGrpcServiceError` instead
53
56
  */
54
- export function isServerErrorResponse(err: unknown): err is ServerErrorResponse {
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';
@@ -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 { isServerErrorResponse, ServiceError } from './errors';
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, opts.scheduleId, 'Failed to create schedule');
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, scheduleId, 'Failed to describe schedule');
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, scheduleId, 'Failed to update schedule');
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, scheduleId, 'Failed to patch schedule');
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, scheduleId, 'Failed to delete schedule');
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
- const response: temporal.api.workflowservice.v1.IListSchedulesResponse = await this.workflowService.listSchedules(
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?.name && {
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, scheduleId: string, fallbackMessage: string): never {
500
- if (isServerErrorResponse(err)) {
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
 
@@ -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 <= 59 ? x : undefined),
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 <= 6 ? x : undefined),
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 Error(`Invalid CalendarSpec component for field ${fieldName}: '${item}' of type '${typeof item}'`);
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 = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
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 = typeof workflowTypeOrFunc === 'string' ? workflowTypeOrFunc : workflowTypeOrFunc.name;
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 Error('Unsupported schedule action');
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 Error('Unsupported schedule action');
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