@temporalio/client
Version:
Temporal.io SDK Client sub-package
530 lines • 23.7 kB
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.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