396 lines
18 KiB
JavaScript
396 lines
18 KiB
JavaScript
"use strict";
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.ScheduleNotFoundError = exports.ScheduleAlreadyRunning = exports.ScheduleClient = void 0;
|
|
const grpc_js_1 = require("@grpc/grpc-js");
|
|
const uuid_1 = require("uuid");
|
|
const payload_search_attributes_1 = require("@temporalio/common/lib/converter/payload-search-attributes");
|
|
const interceptors_1 = require("@temporalio/common/lib/interceptors");
|
|
const internal_non_workflow_1 = require("@temporalio/common/lib/internal-non-workflow");
|
|
const internal_workflow_1 = require("@temporalio/common/lib/internal-workflow");
|
|
const proto_1 = require("@temporalio/proto");
|
|
const time_1 = require("@temporalio/common/lib/time");
|
|
const type_helpers_1 = require("@temporalio/common/lib/type-helpers");
|
|
const errors_1 = require("./errors");
|
|
const schedule_types_1 = require("./schedule-types");
|
|
const schedule_helpers_1 = require("./schedule-helpers");
|
|
const base_client_1 = require("./base-client");
|
|
const helpers_1 = require("./helpers");
|
|
function defaultScheduleClientOptions() {
|
|
return {
|
|
...(0, base_client_1.defaultBaseClientOptions)(),
|
|
interceptors: [],
|
|
};
|
|
}
|
|
function assertRequiredScheduleOptions(opts, action) {
|
|
const structureName = action === 'CREATE' ? 'ScheduleOptions' : 'ScheduleUpdateOptions';
|
|
if (action === 'CREATE' && !opts.scheduleId) {
|
|
throw new TypeError(`Missing ${structureName}.scheduleId`);
|
|
}
|
|
switch (opts.action.type) {
|
|
case 'startWorkflow':
|
|
if (!opts.action.taskQueue) {
|
|
throw new TypeError(`Missing ${structureName}.action.taskQueue for 'startWorkflow' action`);
|
|
}
|
|
if (!opts.action.workflowType) {
|
|
throw new TypeError(`Missing ${structureName}.action.workflowType for 'startWorkflow' action`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Client for starting Workflow executions and creating Workflow handles
|
|
*/
|
|
class ScheduleClient extends base_client_1.BaseClient {
|
|
options;
|
|
constructor(options) {
|
|
super(options);
|
|
this.options = {
|
|
...defaultScheduleClientOptions(),
|
|
...(0, internal_workflow_1.filterNullAndUndefined)(options ?? {}),
|
|
loadedDataConverter: this.dataConverter,
|
|
};
|
|
}
|
|
/**
|
|
* Raw gRPC access to the Temporal service. Schedule-related methods are included in {@link WorkflowService}.
|
|
*
|
|
* **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests made to the service.
|
|
*/
|
|
get workflowService() {
|
|
return this.connection.workflowService;
|
|
}
|
|
async create(options) {
|
|
await this._createSchedule(options);
|
|
return this.getHandle(options.scheduleId);
|
|
}
|
|
/**
|
|
* Create a new Schedule.
|
|
*/
|
|
async _createSchedule(options) {
|
|
assertRequiredScheduleOptions(options, 'CREATE');
|
|
const compiledOptions = (0, schedule_helpers_1.compileScheduleOptions)(options);
|
|
const create = (0, interceptors_1.composeInterceptors)(this.options.interceptors, 'create', this._createScheduleHandler.bind(this));
|
|
await create({
|
|
options: compiledOptions,
|
|
headers: {},
|
|
});
|
|
}
|
|
/**
|
|
* Create a new Schedule.
|
|
*/
|
|
async _createScheduleHandler(input) {
|
|
const { options: opts, headers } = input;
|
|
const { identity } = this.options;
|
|
const req = {
|
|
namespace: this.options.namespace,
|
|
identity,
|
|
requestId: (0, uuid_1.v4)(),
|
|
scheduleId: opts.scheduleId,
|
|
schedule: {
|
|
spec: (0, schedule_helpers_1.encodeScheduleSpec)(opts.spec),
|
|
action: await (0, schedule_helpers_1.encodeScheduleAction)(this.dataConverter, opts.action, headers),
|
|
policies: (0, schedule_helpers_1.encodeSchedulePolicies)(opts.policies),
|
|
state: (0, schedule_helpers_1.encodeScheduleState)(opts.state),
|
|
},
|
|
memo: opts.memo ? { fields: await (0, internal_non_workflow_1.encodeMapToPayloads)(this.dataConverter, opts.memo) } : undefined,
|
|
searchAttributes: opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line @typescript-eslint/no-deprecated
|
|
? {
|
|
indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line @typescript-eslint/no-deprecated
|
|
}
|
|
: undefined,
|
|
initialPatch: {
|
|
triggerImmediately: opts.state?.triggerImmediately
|
|
? { overlapPolicy: proto_1.temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL }
|
|
: undefined,
|
|
backfillRequest: opts.state?.backfill
|
|
? opts.state.backfill.map((x) => ({
|
|
startTime: (0, time_1.optionalDateToTs)(x.start),
|
|
endTime: (0, time_1.optionalDateToTs)(x.end),
|
|
overlapPolicy: x.overlap ? (0, schedule_types_1.encodeScheduleOverlapPolicy)(x.overlap) : undefined,
|
|
}))
|
|
: undefined,
|
|
},
|
|
};
|
|
try {
|
|
const res = await this.workflowService.createSchedule(req);
|
|
return { conflictToken: res.conflictToken };
|
|
}
|
|
catch (err) {
|
|
if (err.code === grpc_js_1.status.ALREADY_EXISTS) {
|
|
throw new ScheduleAlreadyRunning('Schedule already exists and is running', opts.scheduleId);
|
|
}
|
|
this.rethrowGrpcError(err, 'Failed to create schedule', opts.scheduleId);
|
|
}
|
|
}
|
|
/**
|
|
* Describe a Schedule.
|
|
*/
|
|
async _describeSchedule(scheduleId) {
|
|
try {
|
|
return await this.workflowService.describeSchedule({
|
|
namespace: this.options.namespace,
|
|
scheduleId,
|
|
});
|
|
}
|
|
catch (err) {
|
|
this.rethrowGrpcError(err, 'Failed to describe schedule', scheduleId);
|
|
}
|
|
}
|
|
/**
|
|
* Update a Schedule.
|
|
*/
|
|
async _updateSchedule(scheduleId, opts, header) {
|
|
const req = {
|
|
namespace: this.options.namespace,
|
|
scheduleId,
|
|
schedule: {
|
|
spec: (0, schedule_helpers_1.encodeScheduleSpec)(opts.spec),
|
|
action: await (0, schedule_helpers_1.encodeScheduleAction)(this.dataConverter, opts.action, header),
|
|
policies: (0, schedule_helpers_1.encodeSchedulePolicies)(opts.policies),
|
|
state: (0, schedule_helpers_1.encodeScheduleState)(opts.state),
|
|
},
|
|
identity: this.options.identity,
|
|
requestId: (0, uuid_1.v4)(),
|
|
searchAttributes: opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line @typescript-eslint/no-deprecated
|
|
? {
|
|
indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line @typescript-eslint/no-deprecated
|
|
}
|
|
: undefined,
|
|
};
|
|
try {
|
|
return await this.workflowService.updateSchedule(req);
|
|
}
|
|
catch (err) {
|
|
this.rethrowGrpcError(err, 'Failed to update schedule', scheduleId);
|
|
}
|
|
}
|
|
/**
|
|
* Patch a Schedule.
|
|
*/
|
|
async _patchSchedule(scheduleId, patch) {
|
|
try {
|
|
return await this.workflowService.patchSchedule({
|
|
namespace: this.options.namespace,
|
|
scheduleId,
|
|
identity: this.options.identity,
|
|
requestId: (0, uuid_1.v4)(),
|
|
patch,
|
|
});
|
|
}
|
|
catch (err) {
|
|
this.rethrowGrpcError(err, 'Failed to patch schedule', scheduleId);
|
|
}
|
|
}
|
|
/**
|
|
* Delete a Schedule.
|
|
*/
|
|
async _deleteSchedule(scheduleId) {
|
|
try {
|
|
return await this.workflowService.deleteSchedule({
|
|
namespace: this.options.namespace,
|
|
identity: this.options.identity,
|
|
scheduleId,
|
|
});
|
|
}
|
|
catch (err) {
|
|
this.rethrowGrpcError(err, 'Failed to delete schedule', scheduleId);
|
|
}
|
|
}
|
|
/**
|
|
* List Schedules with an `AsyncIterator`:
|
|
*
|
|
* ```ts
|
|
* for await (const schedule: Schedule of client.list()) {
|
|
* const { id, memo, searchAttributes } = schedule
|
|
* // ...
|
|
* }
|
|
* ```
|
|
*
|
|
* To list one page at a time, instead use the raw gRPC method {@link WorkflowService.listSchedules}:
|
|
*
|
|
* ```ts
|
|
* await { schedules, nextPageToken } = client.scheduleService.listSchedules()
|
|
* ```
|
|
*/
|
|
async *list(options) {
|
|
let nextPageToken = undefined;
|
|
for (;;) {
|
|
let response;
|
|
try {
|
|
response = await this.workflowService.listSchedules({
|
|
nextPageToken,
|
|
namespace: this.options.namespace,
|
|
maximumPageSize: options?.pageSize,
|
|
query: options?.query,
|
|
});
|
|
}
|
|
catch (e) {
|
|
this.rethrowGrpcError(e, 'Failed to list schedules', undefined);
|
|
}
|
|
for (const raw of response.schedules ?? []) {
|
|
yield {
|
|
scheduleId: raw.scheduleId,
|
|
spec: (0, schedule_helpers_1.decodeScheduleSpec)(raw.info?.spec ?? {}),
|
|
action: raw.info?.workflowType && {
|
|
type: 'startWorkflow',
|
|
workflowType: raw.info.workflowType.name,
|
|
},
|
|
memo: await (0, internal_non_workflow_1.decodeMapFromPayloads)(this.dataConverter, raw.memo?.fields),
|
|
searchAttributes: (0, payload_search_attributes_1.decodeSearchAttributes)(raw.searchAttributes?.indexedFields),
|
|
typedSearchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields),
|
|
state: {
|
|
paused: raw.info?.paused === true,
|
|
note: raw.info?.notes ?? undefined,
|
|
},
|
|
info: {
|
|
recentActions: (0, schedule_helpers_1.decodeScheduleRecentActions)(raw.info?.recentActions),
|
|
nextActionTimes: raw.info?.futureActionTimes?.map(time_1.tsToDate) ?? [],
|
|
},
|
|
};
|
|
}
|
|
if (response.nextPageToken == null || response.nextPageToken.length === 0)
|
|
break;
|
|
nextPageToken = response.nextPageToken;
|
|
}
|
|
}
|
|
/**
|
|
* Get a handle to a Schedule
|
|
*
|
|
* This method does not validate `scheduleId`. If there is no Schedule with the given `scheduleId`, handle
|
|
* methods like `handle.describe()` will throw a {@link ScheduleNotFoundError} error.
|
|
*/
|
|
getHandle(scheduleId) {
|
|
return {
|
|
client: this,
|
|
scheduleId,
|
|
async describe() {
|
|
const raw = await this.client._describeSchedule(this.scheduleId);
|
|
if (!raw.schedule?.spec || !raw.schedule.action)
|
|
throw new Error('Received invalid Schedule description from server');
|
|
return {
|
|
scheduleId,
|
|
spec: (0, schedule_helpers_1.decodeScheduleSpec)(raw.schedule.spec),
|
|
action: await (0, schedule_helpers_1.decodeScheduleAction)(this.client.dataConverter, raw.schedule.action),
|
|
memo: await (0, internal_non_workflow_1.decodeMapFromPayloads)(this.client.dataConverter, raw.memo?.fields),
|
|
searchAttributes: (0, payload_search_attributes_1.decodeSearchAttributes)(raw.searchAttributes?.indexedFields),
|
|
typedSearchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields),
|
|
policies: {
|
|
// 'overlap' should never be missing on describe, as the server will replace UNSPECIFIED by an actual value
|
|
overlap: (0, schedule_types_1.decodeScheduleOverlapPolicy)(raw.schedule.policies?.overlapPolicy) ?? schedule_types_1.ScheduleOverlapPolicy.SKIP,
|
|
catchupWindow: (0, time_1.optionalTsToMs)(raw.schedule.policies?.catchupWindow) ?? 60_000,
|
|
pauseOnFailure: raw.schedule.policies?.pauseOnFailure === true,
|
|
},
|
|
state: {
|
|
paused: raw.schedule.state?.paused === true,
|
|
note: raw.schedule.state?.notes ?? undefined,
|
|
remainingActions: raw.schedule.state?.limitedActions
|
|
? raw.schedule.state?.remainingActions?.toNumber() || 0
|
|
: undefined,
|
|
},
|
|
info: {
|
|
recentActions: (0, schedule_helpers_1.decodeScheduleRecentActions)(raw.info?.recentActions),
|
|
nextActionTimes: raw.info?.futureActionTimes?.map(time_1.tsToDate) ?? [],
|
|
createdAt: (0, time_1.requiredTsToDate)(raw.info?.createTime, 'createTime'),
|
|
lastUpdatedAt: (0, time_1.optionalTsToDate)(raw.info?.updateTime),
|
|
runningActions: (0, schedule_helpers_1.decodeScheduleRunningActions)(raw.info?.runningWorkflows),
|
|
numActionsMissedCatchupWindow: raw.info?.missedCatchupWindow?.toNumber() ?? 0,
|
|
numActionsSkippedOverlap: raw.info?.overlapSkipped?.toNumber() ?? 0,
|
|
numActionsTaken: raw.info?.actionCount?.toNumber() ?? 0,
|
|
},
|
|
raw,
|
|
};
|
|
},
|
|
async update(updateFn) {
|
|
const current = await this.describe();
|
|
// Keep existing headers
|
|
const currentHeader = current.raw.schedule?.action?.startWorkflow?.header?.fields ?? {};
|
|
const updated = updateFn(current);
|
|
assertRequiredScheduleOptions(updated, 'UPDATE');
|
|
await this.client._updateSchedule(scheduleId, (0, schedule_helpers_1.compileUpdatedScheduleOptions)(scheduleId, updated), currentHeader);
|
|
},
|
|
async delete() {
|
|
await this.client._deleteSchedule(this.scheduleId);
|
|
},
|
|
async pause(note) {
|
|
await this.client._patchSchedule(this.scheduleId, {
|
|
pause: note ?? 'Paused via TypeScript SDK"',
|
|
});
|
|
},
|
|
async unpause(note) {
|
|
await this.client._patchSchedule(this.scheduleId, {
|
|
unpause: note ?? 'Unpaused via TypeScript SDK"',
|
|
});
|
|
},
|
|
async trigger(overlap) {
|
|
await this.client._patchSchedule(this.scheduleId, {
|
|
triggerImmediately: {
|
|
overlapPolicy: overlap
|
|
? (0, schedule_types_1.encodeScheduleOverlapPolicy)(overlap)
|
|
: proto_1.temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL,
|
|
},
|
|
});
|
|
},
|
|
async backfill(options) {
|
|
const backfills = Array.isArray(options) ? options : [options];
|
|
await this.client._patchSchedule(this.scheduleId, {
|
|
backfillRequest: backfills.map((x) => ({
|
|
startTime: (0, time_1.optionalDateToTs)(x.start),
|
|
endTime: (0, time_1.optionalDateToTs)(x.end),
|
|
overlapPolicy: x.overlap ? (0, schedule_types_1.encodeScheduleOverlapPolicy)(x.overlap) : undefined,
|
|
})),
|
|
});
|
|
},
|
|
};
|
|
}
|
|
rethrowGrpcError(err, fallbackMessage, scheduleId) {
|
|
if ((0, errors_1.isGrpcServiceError)(err)) {
|
|
(0, helpers_1.rethrowKnownErrorTypes)(err);
|
|
if (err.code === grpc_js_1.status.NOT_FOUND) {
|
|
throw new ScheduleNotFoundError(err.details ?? 'Schedule not found', scheduleId ?? '');
|
|
}
|
|
if (err.code === grpc_js_1.status.INVALID_ARGUMENT &&
|
|
err.message.match(/^3 INVALID_ARGUMENT: Invalid schedule spec: /)) {
|
|
throw new TypeError(err.message.replace(/^3 INVALID_ARGUMENT: Invalid schedule spec: /, ''));
|
|
}
|
|
throw new errors_1.ServiceError(fallbackMessage, { cause: err });
|
|
}
|
|
throw new errors_1.ServiceError('Unexpected error while making gRPC request', { cause: err });
|
|
}
|
|
}
|
|
exports.ScheduleClient = ScheduleClient;
|
|
/**
|
|
* Thrown from {@link ScheduleClient.create} if there's a running (not deleted) Schedule with the given `id`.
|
|
*/
|
|
let ScheduleAlreadyRunning = class ScheduleAlreadyRunning extends Error {
|
|
scheduleId;
|
|
constructor(message, scheduleId) {
|
|
super(message);
|
|
this.scheduleId = scheduleId;
|
|
}
|
|
};
|
|
exports.ScheduleAlreadyRunning = ScheduleAlreadyRunning;
|
|
exports.ScheduleAlreadyRunning = ScheduleAlreadyRunning = __decorate([
|
|
(0, type_helpers_1.SymbolBasedInstanceOfError)('ScheduleAlreadyRunning')
|
|
], ScheduleAlreadyRunning);
|
|
/**
|
|
* Thrown when a Schedule with the given Id is not known to Temporal Server.
|
|
* It could be because:
|
|
* - Id passed is incorrect
|
|
* - Schedule was deleted
|
|
*/
|
|
let ScheduleNotFoundError = class ScheduleNotFoundError extends Error {
|
|
scheduleId;
|
|
constructor(message, scheduleId) {
|
|
super(message);
|
|
this.scheduleId = scheduleId;
|
|
}
|
|
};
|
|
exports.ScheduleNotFoundError = ScheduleNotFoundError;
|
|
exports.ScheduleNotFoundError = ScheduleNotFoundError = __decorate([
|
|
(0, type_helpers_1.SymbolBasedInstanceOfError)('ScheduleNotFoundError')
|
|
], ScheduleNotFoundError);
|
|
//# sourceMappingURL=schedule-client.js.map
|