UNPKG

@temporalio/client

Version:
396 lines 18.6 kB
"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 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