UNPKG

@temporalio/client

Version:
530 lines 23.7 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.NexusOperationNotFoundError = exports.NexusOperationAlreadyStartedError = exports.NexusOperationFailureError = exports.NexusClient = void 0; const grpc_js_1 = require("@grpc/grpc-js"); const uuid_1 = require("uuid"); const interceptors_1 = require("@temporalio/common/lib/interceptors"); const type_helpers_1 = require("@temporalio/common/lib/type-helpers"); const payload_search_attributes_1 = require("@temporalio/common/lib/converter/payload-search-attributes"); const internal_non_workflow_1 = require("@temporalio/common/lib/internal-non-workflow"); const internal_workflow_1 = require("@temporalio/common/lib/internal-workflow"); const time_1 = require("@temporalio/common/lib/time"); const proto_1 = require("@temporalio/proto"); const encoding_1 = require("@temporalio/common/lib/encoding"); const base_client_1 = require("./base-client"); const errors_1 = require("./errors"); const helpers_1 = require("./helpers"); const nexus_types_1 = require("./nexus-types"); function defaultNexusClientOptions() { return { ...(0, base_client_1.defaultBaseClientOptions)(), interceptors: [], }; } /** * Client for standalone Nexus operations. Access via {@link Client.nexus}. * * Use {@link createServiceClient} to get a typed service client, or call the namespace-wide * {@link list}, {@link count}, and {@link getHandle} methods directly. * * @see {@link Client} * * @experimental Nexus Standalone Operations are experimental. */ class NexusClient extends base_client_1.BaseClient { options; interceptors; constructor(options) { super(options); this.options = { ...defaultNexusClientOptions(), ...(0, internal_workflow_1.filterNullAndUndefined)(options ?? {}), loadedDataConverter: this.dataConverter, }; this.interceptors = this.options.interceptors; } /** * Create a typed service client for starting and executing Nexus operations on a specific endpoint + service. * * @experimental Nexus Standalone Operations are experimental. */ createServiceClient(options) { const { endpoint, service } = options; const startOperation = async (operation, input, options) => { let operationName; if (typeof operation === 'string') { const op = service.operations[operation]; if (op == null) { // The OperationReference<T> type guarantees that if operation is // a string then it is a key of service.operations. This runtime // check is for extra safety. throw new TypeError(`Unable to resolve Nexus operation name from key ${operation} for service ${service.name}`); } operationName = op.name; } else { operationName = operation.name; } const handle = await this.startNexusOperation({ endpoint, service: service.name, operation: operationName, arg: input, id: options.id, scheduleToCloseTimeout: options.scheduleToCloseTimeout, scheduleToStartTimeout: options.scheduleToStartTimeout, startToCloseTimeout: options.startToCloseTimeout, summary: options.summary, idReusePolicy: options.idReusePolicy, idConflictPolicy: options.idConflictPolicy, searchAttributes: options.searchAttributes, headers: options.headers, }); // The interceptor layer returns NexusOperationHandle<unknown>, so reapply // the output type here to match the OperationDefinition contract. return handle; }; const executeOperation = async (operation, input, options) => { const handle = await startOperation(operation, input, options); return await handle.result(); }; return { endpoint, service, startOperation, executeOperation, }; } getHandle(operationId, options) { return this.createNexusOperationHandle({ operationId, runId: options?.runId, }); } /** * List standalone Nexus operations matching a visibility query. * * @experimental Nexus Standalone Operations are experimental. */ list(options) { const input = { query: options?.query, pageSize: options?.pageSize, }; const next = this.listHandler.bind(this); const list = (0, interceptors_1.composeInterceptors)(this.interceptors, 'list', next); return list(input); } /** * Count standalone Nexus operations matching a visibility query. * * @experimental Nexus Standalone Operations are experimental. */ async count(query) { const input = { query }; const next = this.countHandler.bind(this); const count = (0, interceptors_1.composeInterceptors)(this.interceptors, 'count', next); return await count(input); } async startNexusOperation(input) { const next = this.startNexusOperationHandler.bind(this); const start = (0, interceptors_1.composeInterceptors)(this.interceptors, 'startOperation', next); return await start(input); } async getNexusOperationResult(input) { const next = this.getResultHandler.bind(this); const get = (0, interceptors_1.composeInterceptors)(this.interceptors, 'getResult', next); return await get(input); } async describeNexusOperation(input) { const next = this.describeHandler.bind(this); const describe = (0, interceptors_1.composeInterceptors)(this.interceptors, 'describe', next); return await describe(input); } async cancelNexusOperation(input) { const next = this.cancelHandler.bind(this); const cancel = (0, interceptors_1.composeInterceptors)(this.interceptors, 'cancel', next); await cancel(input); } async terminateNexusOperation(input) { const next = this.terminateHandler.bind(this); const terminate = (0, interceptors_1.composeInterceptors)(this.interceptors, 'terminate', next); await terminate(input); } async startNexusOperationHandler(input) { const inputPayload = await (0, internal_non_workflow_1.encodeToPayload)(this.dataConverter, input.arg); const searchAttributes = input.searchAttributes != null ? { indexedFields: (0, payload_search_attributes_1.encodeUnifiedSearchAttributes)(undefined, input.searchAttributes) } : undefined; const userMetadata = input.summary != null ? { summary: await (0, internal_non_workflow_1.encodeToPayload)(this.dataConverter, input.summary), } : undefined; const req = { namespace: this.options.namespace, identity: this.options.identity, requestId: (0, uuid_1.v4)(), operationId: input.id, endpoint: input.endpoint, service: input.service, operation: input.operation, scheduleToCloseTimeout: (0, time_1.msOptionalToTs)(input.scheduleToCloseTimeout), scheduleToStartTimeout: (0, time_1.msOptionalToTs)(input.scheduleToStartTimeout), startToCloseTimeout: (0, time_1.msOptionalToTs)(input.startToCloseTimeout), input: inputPayload, idReusePolicy: input.idReusePolicy != null ? (0, nexus_types_1.encodeNexusOperationIdReusePolicy)(input.idReusePolicy) : undefined, idConflictPolicy: input.idConflictPolicy != null ? (0, nexus_types_1.encodeNexusOperationIdConflictPolicy)(input.idConflictPolicy) : undefined, searchAttributes, nexusHeader: input.headers ?? {}, userMetadata, }; let res; try { res = await this.connection.workflowService.startNexusOperationExecution(req); } catch (err) { this.rethrowGrpcError(err, 'Failed to start Nexus operation', input.id); } return this.createNexusOperationHandle({ operationId: input.id, runId: res.runId ?? undefined, }); } createNexusOperationHandle(opts) { let cachedResult = { state: 'not-requested' }; return { operationId: opts.operationId, runId: opts.runId, client: this, async result() { if (cachedResult.state === 'not-requested') { try { const result = (await this.client.getNexusOperationResult({ operationId: this.operationId, runId: this.runId, })); cachedResult = { state: 'success', value: result }; return result; } catch (err) { if (err instanceof NexusOperationFailureError) { cachedResult = { state: 'failed', failure: err }; } throw err; } } else if (cachedResult.state === 'success') { return cachedResult.value; } else { throw cachedResult.failure; } }, async describe(_options) { return await this.client.describeNexusOperation({ operationId: this.operationId, runId: this.runId, }); }, async cancel(reason) { return await this.client.cancelNexusOperation({ operationId: this.operationId, runId: this.runId, reason, }); }, async terminate(reason) { return await this.client.terminateNexusOperation({ operationId: this.operationId, runId: this.runId, reason, }); }, }; } async getResultHandler(input) { const req = { namespace: this.options.namespace, operationId: input.operationId, runId: input.runId ?? '', waitStage: proto_1.temporal.api.enums.v1.NexusOperationWaitStage.NEXUS_OPERATION_WAIT_STAGE_CLOSED, }; for (;;) { let res; try { res = await this.connection.workflowService.pollNexusOperationExecution(req); } catch (err) { this.rethrowGrpcError(err, 'Failed to poll Nexus operation result', input.operationId); } // The operation is closed if we have a result or failure if (res.result) { return await (0, internal_non_workflow_1.decodeFromPayloadsAtIndex)(this.dataConverter, 0, [res.result]); } if (res.failure) { const cause = await (0, internal_non_workflow_1.decodeOptionalFailureToOptionalError)(this.dataConverter, res.failure); throw new NexusOperationFailureError(`Nexus operation failed: ${res.failure.message ?? 'unknown failure'}`, cause ?? new Error(res.failure.message ?? 'unknown failure')); } } } async describeHandler(input) { const req = { namespace: this.options.namespace, operationId: input.operationId, runId: input.runId ?? '', }; let res; try { res = await this.connection.workflowService.describeNexusOperationExecution(req); } catch (err) { this.rethrowGrpcError(err, 'Failed to describe Nexus operation', input.operationId); } if (!res.info) { throw new errors_1.ServiceError('Received invalid Nexus operation description from server: missing info'); } return await nexusOperationExecutionDescriptionFromProto(res.info, this.dataConverter); } async cancelHandler(input) { const req = { namespace: this.options.namespace, operationId: input.operationId, runId: input.runId ?? '', identity: this.options.identity, requestId: (0, uuid_1.v4)(), reason: input.reason, }; try { await this.connection.workflowService.requestCancelNexusOperationExecution(req); } catch (err) { this.rethrowGrpcError(err, 'Failed to cancel Nexus operation', input.operationId); } } async terminateHandler(input) { const req = { namespace: this.options.namespace, operationId: input.operationId, runId: input.runId ?? '', identity: this.options.identity, requestId: (0, uuid_1.v4)(), reason: input.reason, }; try { await this.connection.workflowService.terminateNexusOperationExecution(req); } catch (err) { this.rethrowGrpcError(err, 'Failed to terminate Nexus operation', input.operationId); } } async *listHandler(input) { let nextPageToken = undefined; for (;;) { let response; try { response = await this.connection.workflowService.listNexusOperationExecutions({ namespace: this.options.namespace, query: input.query, pageSize: input.pageSize, nextPageToken, }); } catch (err) { this.rethrowGrpcError(err, 'Failed to list Nexus operations', undefined); } for (const raw of response.operations ?? []) { yield nexusOperationListInfoFromProto(raw); } if (response.nextPageToken == null || response.nextPageToken.length === 0) break; nextPageToken = response.nextPageToken; } } async countHandler(input) { try { const res = await this.connection.workflowService.countNexusOperationExecutions({ namespace: this.options.namespace, query: input.query, }); return nexusCountFromProto(res); } catch (err) { this.rethrowGrpcError(err, 'Failed to count Nexus operations', undefined); } } rethrowGrpcError(err, fallbackMessage, operationId, runId) { if ((0, errors_1.isGrpcServiceError)(err)) { (0, helpers_1.rethrowKnownErrorTypes)(err); if (err.code === grpc_js_1.status.ALREADY_EXISTS && operationId != null) { throw new NexusOperationAlreadyStartedError(operationId, (0, helpers_1.extractNexusOperationAlreadyStartedRunId)(err)); } if (err.code === grpc_js_1.status.NOT_FOUND && operationId != null) { throw new NexusOperationNotFoundError(operationId, runId); } throw new errors_1.ServiceError(fallbackMessage, { cause: err }); } throw new errors_1.ServiceError('Unexpected error while making gRPC request', { cause: err }); } } exports.NexusClient = NexusClient; async function cancellationInfoFromProto(raw, dataConverter) { return { requestedTime: (0, time_1.optionalTsToDate)(raw.requestedTime), state: (0, nexus_types_1.decodeNexusOperationCancellationState)(raw.state), attempt: raw.attempt ?? 0, lastAttemptCompleteTime: (0, time_1.optionalTsToDate)(raw.lastAttemptCompleteTime), nextAttemptScheduleTime: (0, time_1.optionalTsToDate)(raw.nextAttemptScheduleTime), lastAttemptFailure: await (0, internal_non_workflow_1.decodeOptionalFailureToOptionalError)(dataConverter, raw.lastAttemptFailure), blockedReason: raw.blockedReason ?? undefined, reason: raw.reason ?? '', raw, }; } async function nexusOperationExecutionDescriptionFromProto(raw, dataConverter) { let decodedMetadata = { state: 'pending', }; const decodeMetadata = async () => { if (decodedMetadata.state === 'pending') { const metadataPromise = Promise.all([ (0, internal_non_workflow_1.decodeOptionalSinglePayload)(dataConverter, raw.userMetadata?.summary), (0, internal_non_workflow_1.decodeOptionalSinglePayload)(dataConverter, raw.userMetadata?.details), ]).then(([summary, details]) => { return { summary: summary ?? undefined, details: details ?? undefined, }; }); decodedMetadata = { state: 'requested', value: metadataPromise, }; } return await decodedMetadata.value; }; return { operationId: raw.operationId ?? '', runId: raw.runId ?? '', endpoint: raw.endpoint ?? '', service: raw.service ?? '', operation: raw.operation ?? '', status: (0, nexus_types_1.decodeNexusOperationExecutionStatus)(raw.status), state: (0, nexus_types_1.decodePendingNexusOperationState)(raw.state), scheduleToCloseTimeout: (0, time_1.optionalTsToMs)(raw.scheduleToCloseTimeout), scheduleToStartTimeout: (0, time_1.optionalTsToMs)(raw.scheduleToStartTimeout), startToCloseTimeout: (0, time_1.optionalTsToMs)(raw.startToCloseTimeout), attempt: raw.attempt ?? 0, scheduleTime: (0, time_1.optionalTsToDate)(raw.scheduleTime), expirationTime: (0, time_1.optionalTsToDate)(raw.expirationTime), closeTime: (0, time_1.optionalTsToDate)(raw.closeTime), lastAttemptCompleteTime: (0, time_1.optionalTsToDate)(raw.lastAttemptCompleteTime), lastAttemptFailure: await (0, internal_non_workflow_1.decodeOptionalFailureToOptionalError)(dataConverter, raw.lastAttemptFailure), nextAttemptScheduleTime: (0, time_1.optionalTsToDate)(raw.nextAttemptScheduleTime), executionDuration: (0, time_1.optionalTsToMs)(raw.executionDuration), cancellationInfo: raw.cancellationInfo ? await cancellationInfoFromProto(raw.cancellationInfo, dataConverter) : undefined, blockedReason: raw.blockedReason ?? undefined, requestId: raw.requestId ?? '', operationToken: raw.operationToken ?? undefined, stateTransitionCount: raw.stateTransitionCount?.toNumber() ?? 0, searchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields), identity: raw.identity ?? '', raw, staticDetails: async () => (await decodeMetadata()).details, staticSummary: async () => (await decodeMetadata()).summary, }; } function nexusOperationListInfoFromProto(raw) { return { operationId: raw.operationId ?? '', runId: raw.runId ?? '', endpoint: raw.endpoint ?? '', service: raw.service ?? '', operation: raw.operation ?? '', scheduleTime: (0, time_1.optionalTsToDate)(raw.scheduleTime), closeTime: (0, time_1.optionalTsToDate)(raw.closeTime), status: (0, nexus_types_1.decodeNexusOperationExecutionStatus)(raw.status), searchAttributes: (0, payload_search_attributes_1.decodeTypedSearchAttributes)(raw.searchAttributes?.indexedFields), stateTransitionCount: raw.stateTransitionCount?.toNumber() ?? 0, executionDuration: (0, time_1.optionalTsToMs)(raw.executionDuration), raw, }; } function nexusCountFromProto(raw) { return { count: raw.count?.toNumber() ?? 0, groups: (raw.groups ?? []).map((group) => ({ count: group.count?.toNumber() ?? 0, groupValues: (group.groupValues ?? []).map(decodeCountGroupValue), })), }; } function decodeCountGroupValue(value) { const decoded = payload_search_attributes_1.typedSearchAttributePayloadConverter.fromPayload(value); if (decoded === undefined) { throw new errors_1.ServiceError('Received invalid Nexus operation count group value from server: ' + `metadata.type=${decodePayloadMetadata(value.metadata?.type) ?? '<missing>'}, ` + `metadata.encoding=${decodePayloadMetadata(value.metadata?.encoding) ?? '<missing>'}`); } return decoded; } function decodePayloadMetadata(value) { return value == null ? undefined : (0, encoding_1.decode)(value); } /** * Thrown by {@link NexusOperationHandle.result} when the operation completes with a failure outcome. * The original failure is available on `cause`. */ let NexusOperationFailureError = class NexusOperationFailureError extends Error { cause; constructor(message, cause) { super(message, { cause }); this.cause = cause; } }; exports.NexusOperationFailureError = NexusOperationFailureError; exports.NexusOperationFailureError = NexusOperationFailureError = __decorate([ (0, type_helpers_1.SymbolBasedInstanceOfError)('NexusOperationFailureError') ], NexusOperationFailureError); /** * Thrown by {@link NexusServiceClient.startOperation} when the server returns ALREADY_EXISTS * because an operation with the given ID already exists and the reuse/conflict policies disallow reuse. */ let NexusOperationAlreadyStartedError = class NexusOperationAlreadyStartedError extends Error { operationId; runId; constructor(operationId, runId) { super(`Nexus operation already started: operationId=${operationId}${runId ? ` runId=${runId}` : ''}`); this.operationId = operationId; this.runId = runId; } }; exports.NexusOperationAlreadyStartedError = NexusOperationAlreadyStartedError; exports.NexusOperationAlreadyStartedError = NexusOperationAlreadyStartedError = __decorate([ (0, type_helpers_1.SymbolBasedInstanceOfError)('NexusOperationAlreadyStartedError') ], NexusOperationAlreadyStartedError); /** * Thrown when a Nexus Operation with the given operationId and runId is not known by the Temporal Server. */ let NexusOperationNotFoundError = class NexusOperationNotFoundError extends Error { operationId; runId; constructor(operationId, runId) { super(`Nexus operation not found: operationId=${operationId}${runId ? ` runId=${runId}` : ''}`); this.operationId = operationId; this.runId = runId; } }; exports.NexusOperationNotFoundError = NexusOperationNotFoundError; exports.NexusOperationNotFoundError = NexusOperationNotFoundError = __decorate([ (0, type_helpers_1.SymbolBasedInstanceOfError)('NexusOperationNotFoundError') ], NexusOperationNotFoundError); //# sourceMappingURL=nexus-client.js.map