@temporalio/client
Version:
Temporal.io SDK Client sub-package
292 lines (267 loc) • 10.6 kB
text/typescript
import { status } from '@grpc/grpc-js';
import { filterNullAndUndefined } from '@temporalio/common/lib/internal-non-workflow';
import { assertNever, SymbolBasedInstanceOfError, RequireAtLeastOne } from '@temporalio/common/lib/type-helpers';
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
import { temporal } from '@temporalio/proto';
import { BaseClient, BaseClientOptions, defaultBaseClientOptions, LoadedWithDefaults } from './base-client';
import { WorkflowService } from './types';
import { BuildIdOperation, versionSetsFromProto, WorkerBuildIdVersionSets } from './build-id-types';
import { isGrpcServiceError, ServiceError } from './errors';
import { rethrowKnownErrorTypes } from './helpers';
type IUpdateWorkerBuildIdCompatibilityRequest =
temporal.api.workflowservice.v1.IUpdateWorkerBuildIdCompatibilityRequest;
type GetWorkerTaskReachabilityResponse = temporal.api.workflowservice.v1.GetWorkerTaskReachabilityResponse;
/**
* @experimental
*/
export type TaskQueueClientOptions = BaseClientOptions;
/**
* @experimental
*/
export type LoadedTaskQueueClientOptions = LoadedWithDefaults<TaskQueueClientOptions>;
/**
* A stand-in for a Build Id for unversioned Workers
* @experimental
*/
export const UnversionedBuildId = Symbol.for('__temporal_unversionedBuildId');
export type UnversionedBuildIdType = typeof UnversionedBuildId;
/**
* Client for starting Workflow executions and creating Workflow handles
*
* @experimental
*/
export class TaskQueueClient extends BaseClient {
public readonly options: LoadedTaskQueueClientOptions;
constructor(options?: TaskQueueClientOptions) {
super(options);
this.options = {
...defaultBaseClientOptions(),
...filterNullAndUndefined(options ?? {}),
loadedDataConverter: this.dataConverter,
};
}
/**
* Raw gRPC access to the Temporal service.
*
* **NOTE**: The namespace provided in {@link options} is **not** automatically set on requests
* using this service attribute.
*/
get workflowService(): WorkflowService {
return this.connection.workflowService;
}
/**
* Used to add new Build Ids or otherwise update the relative compatibility of Build Ids as
* defined on a specific task queue for the Worker Versioning feature. For more on this feature,
* see https://docs.temporal.io/workers#worker-versioning
*
* @param taskQueue The task queue to make changes to.
* @param operation The operation to be performed.
*/
public async updateBuildIdCompatibility(taskQueue: string, operation: BuildIdOperation): Promise<void> {
const request: IUpdateWorkerBuildIdCompatibilityRequest = {
namespace: this.options.namespace,
taskQueue,
};
switch (operation.operation) {
case 'addNewIdInNewDefaultSet':
request.addNewBuildIdInNewDefaultSet = operation.buildId;
break;
case 'addNewCompatibleVersion':
request.addNewCompatibleBuildId = {
newBuildId: operation.buildId,
existingCompatibleBuildId: operation.existingCompatibleBuildId,
};
break;
case 'promoteSetByBuildId':
request.promoteSetByBuildId = operation.buildId;
break;
case 'promoteBuildIdWithinSet':
request.promoteBuildIdWithinSet = operation.buildId;
break;
case 'mergeSets':
request.mergeSets = {
primarySetBuildId: operation.primaryBuildId,
secondarySetBuildId: operation.secondaryBuildId,
};
break;
default:
assertNever('Unknown build id update operation', operation);
}
try {
await this.workflowService.updateWorkerBuildIdCompatibility(request);
} catch (e) {
this.rethrowGrpcError(e, 'Unexpected error updating Build Id compatibility');
}
}
/**
* Fetch the sets of compatible Build Ids for a given task queue.
*
* @param taskQueue The task queue to fetch the compatibility information for.
* @returns The sets of compatible Build Ids for the given task queue, or undefined if the queue
* has no Build Ids defined on it.
*/
public async getBuildIdCompatability(taskQueue: string): Promise<WorkerBuildIdVersionSets | undefined> {
let resp;
try {
resp = await this.workflowService.getWorkerBuildIdCompatibility({
taskQueue,
namespace: this.options.namespace,
});
} catch (e) {
this.rethrowGrpcError(e, 'Unexpected error fetching Build Id compatibility');
}
if (resp.majorVersionSets == null || resp.majorVersionSets.length === 0) {
return undefined;
}
return versionSetsFromProto(resp);
}
/**
* Fetches task reachability to determine whether a worker may be retired. The request may specify
* task queues to query for or let the server fetch all task queues mapped to the given build IDs.
*
* When requesting a large number of task queues or all task queues associated with the given
* build ids in a namespace, all task queues will be listed in the response but some of them may
* not contain reachability information due to a server enforced limit. When reaching the limit,
* task queues that reachability information could not be retrieved for will be marked with a
* `NotFetched` entry in {@link BuildIdReachability.taskQueueReachability}. The caller may issue
* another call to get the reachability for those task queues.
*/
public async getReachability(options: ReachabilityOptions): Promise<ReachabilityResponse> {
let resp;
const buildIds = options.buildIds?.map((bid) => {
if (bid === UnversionedBuildId) {
return '';
}
return bid;
});
try {
resp = await this.workflowService.getWorkerTaskReachability({
namespace: this.options.namespace,
taskQueues: options.taskQueues,
buildIds,
reachability: encodeTaskReachability(options.reachability),
});
} catch (e) {
this.rethrowGrpcError(e, 'Unexpected error fetching Build Id reachability');
}
return reachabilityResponseFromProto(resp);
}
protected rethrowGrpcError(err: unknown, fallbackMessage: string): never {
if (isGrpcServiceError(err)) {
rethrowKnownErrorTypes(err);
if (err.code === status.NOT_FOUND) {
throw new BuildIdNotFoundError(err.details ?? 'Build Id not found');
}
throw new ServiceError(fallbackMessage, { cause: err });
}
throw new ServiceError('Unexpected error while making gRPC request');
}
}
/**
* Options for {@link TaskQueueClient.getReachability}
*/
export type ReachabilityOptions = RequireAtLeastOne<BaseReachabilityOptions, 'buildIds' | 'taskQueues'>;
export const ReachabilityType = {
/** The Build Id might be used by new workflows. */
NEW_WORKFLOWS: 'NEW_WORKFLOWS',
/** The Build Id might be used by open workflows and/or closed workflows. */
EXISTING_WORKFLOWS: 'EXISTING_WORKFLOWS',
/** The Build Id might be used by open workflows. */
OPEN_WORKFLOWS: 'OPEN_WORKFLOWS',
/** The Build Id might be used by closed workflows. */
CLOSED_WORKFLOWS: 'CLOSED_WORKFLOWS',
} as const;
/**
* There are different types of reachability:
* - `NEW_WORKFLOWS`: The Build Id might be used by new workflows
* - `EXISTING_WORKFLOWS` The Build Id might be used by open workflows and/or closed workflows.
* - `OPEN_WORKFLOWS` The Build Id might be used by open workflows
* - `CLOSED_WORKFLOWS` The Build Id might be used by closed workflows
*/
export type ReachabilityType = (typeof ReachabilityType)[keyof typeof ReachabilityType];
export const [encodeTaskReachability, decodeTaskReachability] = makeProtoEnumConverters<
temporal.api.enums.v1.TaskReachability,
typeof temporal.api.enums.v1.TaskReachability,
keyof typeof temporal.api.enums.v1.TaskReachability,
typeof ReachabilityType,
'TASK_REACHABILITY_'
>(
{
[ReachabilityType.NEW_WORKFLOWS]: 1,
[ReachabilityType.EXISTING_WORKFLOWS]: 2,
[ReachabilityType.OPEN_WORKFLOWS]: 3,
[ReachabilityType.CLOSED_WORKFLOWS]: 4,
UNSPECIFIED: 0,
} as const,
'TASK_REACHABILITY_'
);
/**
* See {@link ReachabilityOptions}
*/
export interface BaseReachabilityOptions {
/**
* A list of build ids to query the reachability of. Currently, at least one Build Id must be
* specified, but this restriction may be lifted in the future.
*/
buildIds: (string | UnversionedBuildIdType)[];
/**
* A list of task queues with Build Ids defined on them that the request is
* concerned with.
*/
taskQueues?: string[];
/** The kind of reachability this request is concerned with. */
reachability?: ReachabilityType;
}
export interface ReachabilityResponse {
/** Maps Build Ids to their reachability information. */
buildIdReachability: Record<string | UnversionedBuildIdType, BuildIdReachability>;
}
export type ReachabilityTypeResponse = ReachabilityType | 'NOT_FETCHED';
export interface BuildIdReachability {
/**
* Maps Task Queue names to how the Build Id may be reachable from them. If they are not
* reachable, the map value will be an empty array.
*/
taskQueueReachability: Record<string, ReachabilityTypeResponse[]>;
}
export function reachabilityResponseFromProto(resp: GetWorkerTaskReachabilityResponse): ReachabilityResponse {
return {
buildIdReachability: Object.fromEntries(
resp.buildIdReachability.map((bir) => {
const taskQueueReachability: Record<string, ReachabilityTypeResponse[]> = {};
if (bir.taskQueueReachability != null) {
for (const tqr of bir.taskQueueReachability) {
if (tqr.taskQueue == null) {
continue;
}
if (tqr.reachability == null) {
taskQueueReachability[tqr.taskQueue] = [];
continue;
}
taskQueueReachability[tqr.taskQueue] = tqr.reachability.map(
(x) => decodeTaskReachability(x) ?? 'NOT_FETCHED'
);
}
}
let bid: string | UnversionedBuildIdType;
if (bir.buildId) {
bid = bir.buildId;
} else {
bid = UnversionedBuildId;
}
return [bid, { taskQueueReachability }];
})
) as Record<string | UnversionedBuildIdType, BuildIdReachability>,
};
}
/**
* Thrown when one or more Build Ids are not found while using the {@link TaskQueueClient}.
*
* It could be because:
* - Id passed is incorrect
* - Build Id has been scavenged by the server.
*
* @experimental
*/
('BuildIdNotFoundError')
export class BuildIdNotFoundError extends Error {}