@temporalio/client 1.4.4 → 1.5.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.d.ts +5 -32
- package/lib/async-completion-client.js +6 -20
- package/lib/async-completion-client.js.map +1 -1
- package/lib/base-client.d.ts +53 -0
- package/lib/base-client.js +45 -0
- package/lib/base-client.js.map +1 -0
- package/lib/client.d.ts +12 -52
- package/lib/client.js +30 -49
- package/lib/client.js.map +1 -1
- package/lib/connection.d.ts +9 -9
- package/lib/connection.js +4 -3
- package/lib/connection.js.map +1 -1
- package/lib/errors.d.ts +0 -1
- package/lib/errors.js +1 -3
- package/lib/errors.js.map +1 -1
- package/lib/helpers.d.ts +3 -0
- package/lib/helpers.js +63 -0
- package/lib/helpers.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/interceptors.d.ts +46 -10
- package/lib/iterators-utils.d.ts +31 -0
- package/lib/iterators-utils.js +80 -0
- package/lib/iterators-utils.js.map +1 -0
- package/lib/schedule-client.d.ts +175 -0
- package/lib/schedule-client.js +383 -0
- package/lib/schedule-client.js.map +1 -0
- package/lib/schedule-helpers.d.ts +20 -0
- package/lib/schedule-helpers.js +290 -0
- package/lib/schedule-helpers.js.map +1 -0
- package/lib/schedule-types.d.ts +691 -0
- package/lib/schedule-types.js +74 -0
- package/lib/schedule-types.js.map +1 -0
- package/lib/types.d.ts +8 -3
- package/lib/types.js.map +1 -1
- package/lib/workflow-client.d.ts +76 -59
- package/lib/workflow-client.js +87 -101
- package/lib/workflow-client.js.map +1 -1
- package/lib/workflow-options.d.ts +5 -1
- package/lib/workflow-options.js.map +1 -1
- package/package.json +7 -5
- package/src/async-completion-client.ts +16 -55
- package/src/base-client.ts +84 -0
- package/src/client.ts +41 -93
- package/src/connection.ts +12 -11
- package/src/errors.ts +0 -1
- package/src/helpers.ts +75 -0
- package/src/index.ts +2 -0
- package/src/interceptors.ts +54 -10
- package/src/iterators-utils.ts +116 -0
- package/src/schedule-client.ts +541 -0
- package/src/schedule-helpers.ts +414 -0
- package/src/schedule-types.ts +866 -0
- package/src/types.ts +12 -3
- package/src/workflow-client.ts +178 -180
- package/src/workflow-options.ts +12 -1
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { status as grpcStatus } from '@grpc/grpc-js';
|
|
2
|
+
import { v4 as uuid4 } from 'uuid';
|
|
3
|
+
import { mapToPayloads, searchAttributePayloadConverter } from '@temporalio/common';
|
|
4
|
+
import { composeInterceptors, Headers } from '@temporalio/common/lib/interceptors';
|
|
5
|
+
import {
|
|
6
|
+
encodeMapToPayloads,
|
|
7
|
+
decodeMapFromPayloads,
|
|
8
|
+
filterNullAndUndefined,
|
|
9
|
+
} from '@temporalio/common/lib/internal-non-workflow';
|
|
10
|
+
import { temporal } from '@temporalio/proto';
|
|
11
|
+
import { optionalDateToTs, optionalTsToDate, optionalTsToMs, tsToDate } from '@temporalio/common/lib/time';
|
|
12
|
+
import { CreateScheduleInput, CreateScheduleOutput, ScheduleClientInterceptor } from './interceptors';
|
|
13
|
+
import { WorkflowService } from './types';
|
|
14
|
+
import { isServerErrorResponse, ServiceError } from './errors';
|
|
15
|
+
import {
|
|
16
|
+
Backfill,
|
|
17
|
+
CompiledScheduleUpdateOptions,
|
|
18
|
+
ScheduleSummary,
|
|
19
|
+
ScheduleDescription,
|
|
20
|
+
ScheduleOptions,
|
|
21
|
+
ScheduleOverlapPolicy,
|
|
22
|
+
ScheduleUpdateOptions,
|
|
23
|
+
} from './schedule-types';
|
|
24
|
+
import {
|
|
25
|
+
compileScheduleOptions,
|
|
26
|
+
compileUpdatedScheduleOptions,
|
|
27
|
+
decodeOverlapPolicy,
|
|
28
|
+
decodeScheduleAction,
|
|
29
|
+
decodeScheduleRecentActions,
|
|
30
|
+
decodeScheduleRunningActions,
|
|
31
|
+
decodeScheduleSpec,
|
|
32
|
+
decodeSearchAttributes,
|
|
33
|
+
encodeOverlapPolicy,
|
|
34
|
+
encodeScheduleAction,
|
|
35
|
+
encodeSchedulePolicies,
|
|
36
|
+
encodeScheduleSpec,
|
|
37
|
+
encodeScheduleState,
|
|
38
|
+
} from './schedule-helpers';
|
|
39
|
+
import {
|
|
40
|
+
BaseClient,
|
|
41
|
+
BaseClientOptions,
|
|
42
|
+
defaultBaseClientOptions,
|
|
43
|
+
LoadedWithDefaults,
|
|
44
|
+
WithDefaults,
|
|
45
|
+
} from './base-client';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handle to a single Schedule
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
export interface ScheduleHandle {
|
|
53
|
+
/**
|
|
54
|
+
* This Schedule's identifier
|
|
55
|
+
*/
|
|
56
|
+
readonly scheduleId: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fetch the Schedule's description from the Server
|
|
60
|
+
*/
|
|
61
|
+
describe(): Promise<ScheduleDescription>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Update the Schedule
|
|
65
|
+
*
|
|
66
|
+
* This function calls `.describe()`, provides the `Schedule` to the provided `updateFn`, and
|
|
67
|
+
* sends the returned `UpdatedSchedule` to the Server to update the Schedule definition. Note that,
|
|
68
|
+
* in the future, `updateFn` might be invoked multiple time, with identical or different input.
|
|
69
|
+
*/
|
|
70
|
+
update(updateFn: (previous: ScheduleDescription) => ScheduleUpdateOptions): Promise<void>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Delete the Schedule
|
|
74
|
+
*/
|
|
75
|
+
delete(): Promise<void>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Trigger an Action to be taken immediately
|
|
79
|
+
*
|
|
80
|
+
* @param overlap Override the Overlap Policy for this one trigger. Defaults to {@link ScheduleOverlapPolicy.ALLOW_ALL}.
|
|
81
|
+
*/
|
|
82
|
+
trigger(overlap?: ScheduleOverlapPolicy): Promise<void>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Run though the specified time period(s) and take Actions as if that time passed by right now, all at once.
|
|
86
|
+
* The Overlap Policy can be overridden for the scope of the Backfill.
|
|
87
|
+
*/
|
|
88
|
+
backfill(options: Backfill | Backfill[]): Promise<void>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pause the Schedule
|
|
92
|
+
*
|
|
93
|
+
* @param note A new {@link ScheduleDescription.note}. Defaults to `"Paused via TypeScript SDK"`
|
|
94
|
+
*/
|
|
95
|
+
pause(note?: string): Promise<void>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Unpause the Schedule
|
|
99
|
+
*
|
|
100
|
+
* @param note A new {@link ScheduleDescription.note}. Defaults to `"Unpaused via TypeScript SDK"
|
|
101
|
+
*/
|
|
102
|
+
unpause(note?: string): Promise<void>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Readonly accessor to the underlying ScheduleClient
|
|
106
|
+
*/
|
|
107
|
+
readonly client: ScheduleClient;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @experimental
|
|
112
|
+
*/
|
|
113
|
+
export interface ScheduleClientOptions extends BaseClientOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Used to override and extend default Connection functionality
|
|
116
|
+
*
|
|
117
|
+
* Useful for injecting auth headers and tracing Workflow executions
|
|
118
|
+
*/
|
|
119
|
+
interceptors?: ScheduleClientInterceptor[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @experimental */
|
|
123
|
+
export type LoadedScheduleClientOptions = LoadedWithDefaults<ScheduleClientOptions>;
|
|
124
|
+
|
|
125
|
+
function defaultScheduleClientOptions(): WithDefaults<ScheduleClientOptions> {
|
|
126
|
+
return {
|
|
127
|
+
...defaultBaseClientOptions(),
|
|
128
|
+
interceptors: [],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertRequiredScheduleOptions(opts: ScheduleOptions, action: 'CREATE'): void;
|
|
133
|
+
function assertRequiredScheduleOptions(opts: ScheduleUpdateOptions, action: 'UPDATE'): void;
|
|
134
|
+
function assertRequiredScheduleOptions(
|
|
135
|
+
opts: ScheduleOptions | ScheduleUpdateOptions,
|
|
136
|
+
action: 'CREATE' | 'UPDATE'
|
|
137
|
+
): void {
|
|
138
|
+
const structureName = action === 'CREATE' ? 'ScheduleOptions' : 'ScheduleUpdateOptions';
|
|
139
|
+
if (action === 'CREATE' && !(opts as ScheduleOptions).scheduleId) {
|
|
140
|
+
throw new TypeError(`Missing ${structureName}.scheduleId`);
|
|
141
|
+
}
|
|
142
|
+
if (!(opts.spec.calendars?.length || opts.spec.intervals?.length || opts.spec.cronExpressions?.length)) {
|
|
143
|
+
throw new TypeError(`At least one ${structureName}.spec.calendars, .intervals or .cronExpressions is required`);
|
|
144
|
+
}
|
|
145
|
+
switch (opts.action.type) {
|
|
146
|
+
case 'startWorkflow':
|
|
147
|
+
if (!opts.action.taskQueue) {
|
|
148
|
+
throw new TypeError(`Missing ${structureName}.action.taskQueue for 'startWorkflow' action`);
|
|
149
|
+
}
|
|
150
|
+
if (!opts.action.workflowId && action === 'UPDATE') {
|
|
151
|
+
throw new TypeError(`Missing ${structureName}.action.workflowId for 'startWorkflow' action`);
|
|
152
|
+
}
|
|
153
|
+
if (!opts.action.workflowType) {
|
|
154
|
+
throw new TypeError(`Missing ${structureName}.action.workflowType for 'startWorkflow' action`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @experimental */
|
|
160
|
+
export interface ListScheduleOptions {
|
|
161
|
+
/**
|
|
162
|
+
* How many results to fetch from the Server at a time.
|
|
163
|
+
* @default 1000
|
|
164
|
+
*/
|
|
165
|
+
pageSize?: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Client for starting Workflow executions and creating Workflow handles
|
|
170
|
+
*
|
|
171
|
+
* @experimental
|
|
172
|
+
*/
|
|
173
|
+
export class ScheduleClient extends BaseClient {
|
|
174
|
+
public readonly options: LoadedScheduleClientOptions;
|
|
175
|
+
|
|
176
|
+
constructor(options?: ScheduleClientOptions) {
|
|
177
|
+
super(options);
|
|
178
|
+
this.options = {
|
|
179
|
+
...defaultScheduleClientOptions(),
|
|
180
|
+
...filterNullAndUndefined(options ?? {}),
|
|
181
|
+
loadedDataConverter: this.dataConverter,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Raw gRPC access to the Temporal service. Schedule-related methods are included in {@link WorkflowService}.
|
|
187
|
+
*
|
|
188
|
+
* **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service.
|
|
189
|
+
*/
|
|
190
|
+
get workflowService(): WorkflowService {
|
|
191
|
+
return this.connection.workflowService;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Create a new Schedule.
|
|
196
|
+
*
|
|
197
|
+
* @throws {@link ScheduleAlreadyRunning} if there's a running (not deleted) Schedule with the given `id`
|
|
198
|
+
* @returns a ScheduleHandle to the created Schedule
|
|
199
|
+
*/
|
|
200
|
+
public async create(options: ScheduleOptions): Promise<ScheduleHandle> {
|
|
201
|
+
await this._createSchedule(options);
|
|
202
|
+
return this.getHandle(options.scheduleId);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create a new Schedule.
|
|
207
|
+
*/
|
|
208
|
+
protected async _createSchedule(options: ScheduleOptions): Promise<void> {
|
|
209
|
+
assertRequiredScheduleOptions(options, 'CREATE');
|
|
210
|
+
const compiledOptions = compileScheduleOptions(options);
|
|
211
|
+
|
|
212
|
+
const create = composeInterceptors(this.options.interceptors, 'create', this._createScheduleHandler.bind(this));
|
|
213
|
+
await create({
|
|
214
|
+
options: compiledOptions,
|
|
215
|
+
headers: {},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create a new Schedule.
|
|
221
|
+
*/
|
|
222
|
+
protected async _createScheduleHandler(input: CreateScheduleInput): Promise<CreateScheduleOutput> {
|
|
223
|
+
const { options: opts, headers } = input;
|
|
224
|
+
const { identity } = this.options;
|
|
225
|
+
const req: temporal.api.workflowservice.v1.ICreateScheduleRequest = {
|
|
226
|
+
namespace: this.options.namespace,
|
|
227
|
+
identity,
|
|
228
|
+
requestId: uuid4(),
|
|
229
|
+
scheduleId: opts.scheduleId,
|
|
230
|
+
schedule: {
|
|
231
|
+
spec: encodeScheduleSpec(opts.spec),
|
|
232
|
+
action: await encodeScheduleAction(this.dataConverter, opts.action, headers),
|
|
233
|
+
policies: encodeSchedulePolicies(opts.policies),
|
|
234
|
+
state: encodeScheduleState(opts.state),
|
|
235
|
+
},
|
|
236
|
+
memo: opts.memo ? { fields: await encodeMapToPayloads(this.dataConverter, opts.memo) } : undefined,
|
|
237
|
+
searchAttributes: opts.searchAttributes
|
|
238
|
+
? {
|
|
239
|
+
indexedFields: mapToPayloads(searchAttributePayloadConverter, opts.searchAttributes),
|
|
240
|
+
}
|
|
241
|
+
: undefined,
|
|
242
|
+
initialPatch: {
|
|
243
|
+
triggerImmediately: opts.state?.triggerImmediately
|
|
244
|
+
? { overlapPolicy: temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL }
|
|
245
|
+
: undefined,
|
|
246
|
+
backfillRequest: opts.state?.backfill
|
|
247
|
+
? opts.state.backfill.map((x) => ({
|
|
248
|
+
startTime: optionalDateToTs(x.start),
|
|
249
|
+
endTime: optionalDateToTs(x.end),
|
|
250
|
+
overlapPolicy: x.overlap ? encodeOverlapPolicy(x.overlap) : undefined,
|
|
251
|
+
}))
|
|
252
|
+
: undefined,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
const res = await this.workflowService.createSchedule(req);
|
|
257
|
+
return { conflictToken: res.conflictToken };
|
|
258
|
+
} catch (err: any) {
|
|
259
|
+
if (err.code === grpcStatus.ALREADY_EXISTS) {
|
|
260
|
+
throw new ScheduleAlreadyRunning('Schedule already exists and is running', opts.scheduleId);
|
|
261
|
+
}
|
|
262
|
+
this.rethrowGrpcError(err, opts.scheduleId, 'Failed to create schedule');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Describe a Schedule.
|
|
268
|
+
*/
|
|
269
|
+
protected async _describeSchedule(
|
|
270
|
+
scheduleId: string
|
|
271
|
+
): Promise<temporal.api.workflowservice.v1.IDescribeScheduleResponse> {
|
|
272
|
+
try {
|
|
273
|
+
return await this.workflowService.describeSchedule({
|
|
274
|
+
namespace: this.options.namespace,
|
|
275
|
+
scheduleId,
|
|
276
|
+
});
|
|
277
|
+
} catch (err: any) {
|
|
278
|
+
this.rethrowGrpcError(err, scheduleId, 'Failed to describe schedule');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Update a Schedule.
|
|
284
|
+
*/
|
|
285
|
+
protected async _updateSchedule(
|
|
286
|
+
scheduleId: string,
|
|
287
|
+
opts: CompiledScheduleUpdateOptions,
|
|
288
|
+
header: Headers
|
|
289
|
+
): Promise<temporal.api.workflowservice.v1.IUpdateScheduleResponse> {
|
|
290
|
+
try {
|
|
291
|
+
return await this.workflowService.updateSchedule({
|
|
292
|
+
namespace: this.options.namespace,
|
|
293
|
+
scheduleId,
|
|
294
|
+
schedule: {
|
|
295
|
+
spec: encodeScheduleSpec(opts.spec),
|
|
296
|
+
action: await encodeScheduleAction(this.dataConverter, opts.action, header),
|
|
297
|
+
policies: encodeSchedulePolicies(opts.policies),
|
|
298
|
+
state: encodeScheduleState(opts.state),
|
|
299
|
+
},
|
|
300
|
+
identity: this.options.identity,
|
|
301
|
+
requestId: uuid4(),
|
|
302
|
+
});
|
|
303
|
+
} catch (err: any) {
|
|
304
|
+
this.rethrowGrpcError(err, scheduleId, 'Failed to update schedule');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Patch a Schedule.
|
|
310
|
+
*/
|
|
311
|
+
protected async _patchSchedule(
|
|
312
|
+
scheduleId: string,
|
|
313
|
+
patch: temporal.api.schedule.v1.ISchedulePatch
|
|
314
|
+
): Promise<temporal.api.workflowservice.v1.IPatchScheduleResponse> {
|
|
315
|
+
try {
|
|
316
|
+
return await this.workflowService.patchSchedule({
|
|
317
|
+
namespace: this.options.namespace,
|
|
318
|
+
scheduleId,
|
|
319
|
+
identity: this.options.identity,
|
|
320
|
+
requestId: uuid4(),
|
|
321
|
+
patch,
|
|
322
|
+
});
|
|
323
|
+
} catch (err: any) {
|
|
324
|
+
this.rethrowGrpcError(err, scheduleId, 'Failed to patch schedule');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Delete a Schedule.
|
|
330
|
+
*/
|
|
331
|
+
protected async _deleteSchedule(
|
|
332
|
+
scheduleId: string
|
|
333
|
+
): Promise<temporal.api.workflowservice.v1.IDeleteScheduleResponse> {
|
|
334
|
+
try {
|
|
335
|
+
return await this.workflowService.deleteSchedule({
|
|
336
|
+
namespace: this.options.namespace,
|
|
337
|
+
identity: this.options.identity,
|
|
338
|
+
scheduleId,
|
|
339
|
+
});
|
|
340
|
+
} catch (err: any) {
|
|
341
|
+
this.rethrowGrpcError(err, scheduleId, 'Failed to delete schedule');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* List Schedules with an `AsyncIterator`:
|
|
347
|
+
*
|
|
348
|
+
* ```ts
|
|
349
|
+
* for await (const schedule: Schedule of client.list()) {
|
|
350
|
+
* const { id, memo, searchAttributes } = schedule
|
|
351
|
+
* // ...
|
|
352
|
+
* }
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* To list one page at a time, instead use the raw gRPC method {@link WorkflowService.listSchedules}:
|
|
356
|
+
*
|
|
357
|
+
* ```ts
|
|
358
|
+
* await { schedules, nextPageToken } = client.scheduleService.listSchedules()
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
public async *list(options?: ListScheduleOptions): AsyncIterable<ScheduleSummary> {
|
|
362
|
+
let nextPageToken: Uint8Array | undefined = undefined;
|
|
363
|
+
for (;;) {
|
|
364
|
+
const response: temporal.api.workflowservice.v1.IListSchedulesResponse = await this.workflowService.listSchedules(
|
|
365
|
+
{
|
|
366
|
+
nextPageToken,
|
|
367
|
+
namespace: this.options.namespace,
|
|
368
|
+
maximumPageSize: options?.pageSize,
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
for (const raw of response.schedules ?? []) {
|
|
373
|
+
if (!raw.info?.spec) continue;
|
|
374
|
+
|
|
375
|
+
yield <ScheduleSummary>{
|
|
376
|
+
scheduleId: raw.scheduleId,
|
|
377
|
+
|
|
378
|
+
spec: decodeScheduleSpec(raw.info.spec),
|
|
379
|
+
action: {
|
|
380
|
+
type: 'startWorkflow',
|
|
381
|
+
workflowType: raw.info.workflowType?.name,
|
|
382
|
+
},
|
|
383
|
+
memo: await decodeMapFromPayloads(this.dataConverter, raw.memo?.fields),
|
|
384
|
+
searchAttributes: decodeSearchAttributes(raw.searchAttributes),
|
|
385
|
+
state: {
|
|
386
|
+
paused: raw.info.paused === true,
|
|
387
|
+
note: raw.info.notes ?? undefined,
|
|
388
|
+
},
|
|
389
|
+
info: {
|
|
390
|
+
recentActions: decodeScheduleRecentActions(raw.info.recentActions),
|
|
391
|
+
nextActionTimes: raw.info?.futureActionTimes?.map(tsToDate) ?? [],
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (response.nextPageToken == null || response.nextPageToken.length === 0) break;
|
|
397
|
+
nextPageToken = response.nextPageToken;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Get a handle to a Schedule
|
|
403
|
+
*
|
|
404
|
+
* This method does not validate `scheduleId`. If there is no Schedule with the given `scheduleId`, handle
|
|
405
|
+
* methods like `handle.describe()` will throw a {@link ScheduleNotFoundError} error.
|
|
406
|
+
*/
|
|
407
|
+
public getHandle(scheduleId: string): ScheduleHandle {
|
|
408
|
+
return {
|
|
409
|
+
client: this,
|
|
410
|
+
scheduleId,
|
|
411
|
+
|
|
412
|
+
async describe(): Promise<ScheduleDescription> {
|
|
413
|
+
const raw = await this.client._describeSchedule(this.scheduleId);
|
|
414
|
+
if (!raw.schedule?.spec || !raw.schedule.action)
|
|
415
|
+
throw new Error('Received invalid Schedule description from server');
|
|
416
|
+
return {
|
|
417
|
+
scheduleId,
|
|
418
|
+
spec: decodeScheduleSpec(raw.schedule.spec),
|
|
419
|
+
action: await decodeScheduleAction(this.client.dataConverter, raw.schedule.action),
|
|
420
|
+
memo: await decodeMapFromPayloads(this.client.dataConverter, raw.memo?.fields),
|
|
421
|
+
searchAttributes: decodeSearchAttributes(raw.searchAttributes),
|
|
422
|
+
policies: {
|
|
423
|
+
overlap: decodeOverlapPolicy(raw.schedule.policies?.overlapPolicy),
|
|
424
|
+
catchupWindow: optionalTsToMs(raw.schedule.policies?.catchupWindow) ?? 60_000,
|
|
425
|
+
pauseOnFailure: raw.schedule.policies?.pauseOnFailure === true,
|
|
426
|
+
},
|
|
427
|
+
state: {
|
|
428
|
+
paused: raw.schedule.state?.paused === true,
|
|
429
|
+
note: raw.schedule.state?.notes ?? undefined,
|
|
430
|
+
remainingActions: raw.schedule.state?.limitedActions
|
|
431
|
+
? raw.schedule.state?.remainingActions?.toNumber() || 0
|
|
432
|
+
: undefined,
|
|
433
|
+
},
|
|
434
|
+
info: {
|
|
435
|
+
recentActions: decodeScheduleRecentActions(raw.info?.recentActions),
|
|
436
|
+
nextActionTimes: raw.info?.futureActionTimes?.map(tsToDate) ?? [],
|
|
437
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
438
|
+
createdAt: tsToDate(raw.info!.createTime!),
|
|
439
|
+
lastUpdatedAt: optionalTsToDate(raw.info?.updateTime),
|
|
440
|
+
runningActions: decodeScheduleRunningActions(raw.info?.runningWorkflows),
|
|
441
|
+
numActionsMissedCatchupWindow: raw.info?.missedCatchupWindow?.toNumber() ?? 0,
|
|
442
|
+
numActionsSkippedOverlap: raw.info?.overlapSkipped?.toNumber() ?? 0,
|
|
443
|
+
numActionsTaken: raw.info?.actionCount?.toNumber() ?? 0,
|
|
444
|
+
},
|
|
445
|
+
raw,
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
async update(updateFn): Promise<void> {
|
|
450
|
+
const current = await this.describe();
|
|
451
|
+
// Keep existing headers
|
|
452
|
+
const currentHeader: Headers = current.raw.schedule?.action?.startWorkflow?.header?.fields ?? {};
|
|
453
|
+
const updated = updateFn(current);
|
|
454
|
+
assertRequiredScheduleOptions(updated, 'UPDATE');
|
|
455
|
+
await this.client._updateSchedule(scheduleId, compileUpdatedScheduleOptions(updated), currentHeader);
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
async delete(): Promise<void> {
|
|
459
|
+
await this.client._deleteSchedule(this.scheduleId);
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
async pause(note?: string): Promise<void> {
|
|
463
|
+
await this.client._patchSchedule(this.scheduleId, {
|
|
464
|
+
pause: note ?? 'Paused via TypeScript SDK"',
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
async unpause(note?): Promise<void> {
|
|
469
|
+
await this.client._patchSchedule(this.scheduleId, {
|
|
470
|
+
unpause: note ?? 'Unpaused via TypeScript SDK"',
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
async trigger(overlap?): Promise<void> {
|
|
475
|
+
await this.client._patchSchedule(this.scheduleId, {
|
|
476
|
+
triggerImmediately: {
|
|
477
|
+
overlapPolicy: overlap
|
|
478
|
+
? encodeOverlapPolicy(overlap)
|
|
479
|
+
: temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
async backfill(options): Promise<void> {
|
|
485
|
+
const backfills = Array.isArray(options) ? options : [options];
|
|
486
|
+
await this.client._patchSchedule(this.scheduleId, {
|
|
487
|
+
backfillRequest: backfills.map((x) => ({
|
|
488
|
+
startTime: optionalDateToTs(x.start),
|
|
489
|
+
endTime: optionalDateToTs(x.end),
|
|
490
|
+
overlapPolicy: x.overlap ? encodeOverlapPolicy(x.overlap) : undefined,
|
|
491
|
+
})),
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
protected rethrowGrpcError(err: unknown, scheduleId: string, fallbackMessage: string): never {
|
|
498
|
+
if (isServerErrorResponse(err)) {
|
|
499
|
+
if (err.code === grpcStatus.NOT_FOUND) {
|
|
500
|
+
throw new ScheduleNotFoundError(err.details ?? 'Schedule not found', scheduleId);
|
|
501
|
+
}
|
|
502
|
+
if (
|
|
503
|
+
err.code === grpcStatus.INVALID_ARGUMENT &&
|
|
504
|
+
err.message.match(/^3 INVALID_ARGUMENT: Invalid schedule spec: /)
|
|
505
|
+
) {
|
|
506
|
+
throw new TypeError(err.message.replace(/^3 INVALID_ARGUMENT: Invalid schedule spec: /, ''));
|
|
507
|
+
}
|
|
508
|
+
throw new ServiceError(fallbackMessage, { cause: err });
|
|
509
|
+
}
|
|
510
|
+
throw new ServiceError('Unexpected error while making gRPC request');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Thrown from {@link ScheduleClient.create} if there's a running (not deleted) Schedule with the given `id`.
|
|
516
|
+
*
|
|
517
|
+
* @experimental
|
|
518
|
+
*/
|
|
519
|
+
export class ScheduleAlreadyRunning extends Error {
|
|
520
|
+
public readonly name: string = 'ScheduleAlreadyRunning';
|
|
521
|
+
|
|
522
|
+
constructor(message: string, public readonly scheduleId: string) {
|
|
523
|
+
super(message);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Thrown when a Schedule with the given Id is not known to Temporal Server.
|
|
529
|
+
* It could be because:
|
|
530
|
+
* - Id passed is incorrect
|
|
531
|
+
* - Schedule was deleted
|
|
532
|
+
*
|
|
533
|
+
* @experimental
|
|
534
|
+
*/
|
|
535
|
+
export class ScheduleNotFoundError extends Error {
|
|
536
|
+
public readonly name: string = 'ScheduleNotFoundError';
|
|
537
|
+
|
|
538
|
+
constructor(message: string, public readonly scheduleId: string) {
|
|
539
|
+
super(message);
|
|
540
|
+
}
|
|
541
|
+
}
|