@temporalio/client
Version:
Temporal.io SDK Client sub-package
396 lines • 18.6 kB
JavaScript
;
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 deprecation/deprecation
? {
indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
}
: 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 deprecation/deprecation
? {
indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
}
: 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