Socket
Socket
Sign inDemoInstall

@temporalio/client

Package Overview
Dependencies
Maintainers
8
Versions
80
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@temporalio/client - npm Package Compare versions

Comparing version 1.8.6 to 1.9.0-rc.0

11

lib/base-client.d.ts

@@ -0,1 +1,3 @@

/// <reference types="node" />
import 'abort-controller/polyfill';
import { DataConverter, LoadedDataConverter } from '@temporalio/common';

@@ -45,2 +47,11 @@ import { ConnectionLike, Metadata } from './types';

/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing service requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @see {@link Connection.withAbortSignal}
*/
withAbortSignal<R>(abortSignal: AbortSignal, fn: () => Promise<R>): Promise<R>;
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -47,0 +58,0 @@ *

@@ -7,2 +7,4 @@ "use strict";

exports.BaseClient = exports.defaultBaseClientOptions = void 0;
// Keep this around until we drop support for Node 14.
require("abort-controller/polyfill"); // eslint-disable-line import/no-unassigned-import
const node_os_1 = __importDefault(require("node:os"));

@@ -32,2 +34,13 @@ const internal_non_workflow_1 = require("@temporalio/common/lib/internal-non-workflow");

/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing service requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @see {@link Connection.withAbortSignal}
*/
async withAbortSignal(abortSignal, fn) {
return await this.connection.withAbortSignal(abortSignal, fn);
}
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -34,0 +47,0 @@ *

69

lib/connection.d.ts
/// <reference types="node" />
/// <reference types="node" />
import 'abort-controller/polyfill';
import { AsyncLocalStorage } from 'node:async_hooks';

@@ -20,12 +22,24 @@ import * as grpc from '@grpc/grpc-js';

/**
* TLS configuration.
* Pass a falsy value to use a non-encrypted connection or `true` or `{}` to
* connect with TLS without any customization.
* TLS configuration. Pass a falsy value to use a non-encrypted connection,
* or `true` or `{}` to connect with TLS without any customization.
*
* For advanced scenario, a prebuilt {@link grpc.ChannelCredentials} object
* may instead be specified using the {@link credentials} property.
*
* Either {@link credentials} or this may be specified for configuring TLS
*
* @default TLS is disabled
*/
tls?: TLSConfig | boolean | null;
/**
* Channel credentials, create using the factory methods defined {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
* gRPC channel credentials.
*
* `ChannelCredentials` are things like SSL credentials that can be used to secure a connection.
* There may be only one `ChannelCredentials`. They can be created using some of the factory
* methods defined {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
*
* Specifying a prebuilt `ChannelCredentials` should only be required for advanced use cases.
* For simple TLS use cases, using the {@link tls} property is recommended. To register
* `CallCredentials` (eg. metadata-based authentication), use the {@link callCredentials} property.
*
* Either {@link tls} or this may be specified for configuring TLS

@@ -35,2 +49,15 @@ */

/**
* gRPC call credentials.
*
* `CallCredentials` generaly modify metadata; they can be attached to a connection to affect all method
* calls made using that connection. They can be created using some of the factory methods defined
* {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
*
* If `callCredentials` are specified, they will be composed with channel credentials
* (either the one created implicitely by using the {@link tls} option, or the one specified
* explicitly through {@link credentials}). Notice that gRPC doesn't allow registering
* `callCredentials` on insecure connections.
*/
callCredentials?: grpc.CallCredentials[];
/**
* GRPC Channel arguments

@@ -79,3 +106,3 @@ *

}
export type ConnectionOptionsWithDefaults = Required<Omit<ConnectionOptions, 'tls' | 'connectTimeout'>> & {
export type ConnectionOptionsWithDefaults = Required<Omit<ConnectionOptions, 'tls' | 'connectTimeout' | 'callCredentials'>> & {
connectTimeoutMs: number;

@@ -135,2 +162,8 @@ };

* {@link https://github.com/temporalio/api/blob/master/temporal/api/operatorservice/v1/service.proto | Operator service}
*
* The Operator Service API defines how Temporal SDKs and other clients interact with the Temporal
* server to perform administrative functions like registering a search attribute or a namespace.
*
* This Service API is NOT compatible with Temporal Cloud. Attempt to use it against a Temporal
* Cloud namespace will result in gRPC `unauthorized` error.
*/

@@ -173,2 +206,18 @@ readonly operatorService: OperatorService;

/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @example
*
* ```ts
* const ctrl = new AbortController();
* setTimeout(() => ctrl.abort(), 10_000);
* // 👇 throws if incomplete by the timeout.
* await conn.withAbortSignal(ctrl.signal, () => client.workflow.execute(myWorkflow, options));
* ```
*/
withAbortSignal<ReturnType>(abortSignal: AbortSignal, fn: () => Promise<ReturnType>): Promise<ReturnType>;
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -183,7 +232,7 @@ *

*
*```ts
*const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
*);
*```
* ```ts
* const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
* );
* ```
*/

@@ -190,0 +239,0 @@ withMetadata<ReturnType>(metadata: Metadata, fn: () => Promise<ReturnType>): Promise<ReturnType>;

53

lib/connection.js

@@ -30,2 +30,4 @@ "use strict";

exports.Connection = exports.LOCAL_TARGET = void 0;
// Keep this around until we drop support for Node 14.
require("abort-controller/polyfill"); // eslint-disable-line import/no-unassigned-import
const node_async_hooks_1 = require("node:async_hooks");

@@ -63,3 +65,3 @@ const grpc = __importStar(require("@grpc/grpc-js"));

function normalizeGRPCConfig(options) {
const { tls: tlsFromConfig, credentials, ...rest } = options || {};
const { tls: tlsFromConfig, credentials, callCredentials, ...rest } = options || {};
if (rest.address) {

@@ -78,3 +80,3 @@ // eslint-disable-next-line prefer-const

...rest,
credentials: grpc.credentials.createSsl(tls.serverRootCACertificate, tls.clientCertPair?.key, tls.clientCertPair?.crt),
credentials: grpc.credentials.combineChannelCredentials(grpc.credentials.createSsl(tls.serverRootCACertificate, tls.clientCertPair?.key, tls.clientCertPair?.crt), ...(callCredentials ?? [])),
channelArgs: {

@@ -92,3 +94,6 @@ ...rest.channelArgs,

else {
return rest;
return {
...rest,
credentials: grpc.credentials.combineChannelCredentials(credentials ?? grpc.credentials.createInsecure(), ...(callCredentials ?? [])),
};
}

@@ -206,3 +211,3 @@ }

const metadataContainer = new grpc.Metadata();
const { metadata, deadline } = callContextStorage.getStore() ?? {};
const { metadata, deadline, abortSignal } = callContextStorage.getStore() ?? {};
for (const [k, v] of Object.entries(staticMetadata)) {

@@ -216,3 +221,7 @@ metadataContainer.set(k, v);

}
return client.makeUnaryRequest(`/${serviceName}/${method.name}`, (arg) => arg, (arg) => arg, requestData, metadataContainer, { interceptors, deadline }, callback);
const call = client.makeUnaryRequest(`/${serviceName}/${method.name}`, (arg) => arg, (arg) => arg, requestData, metadataContainer, { interceptors, deadline }, callback);
if (abortSignal != null) {
abortSignal.addEventListener('abort', () => call.cancel());
}
return call;
};

@@ -227,5 +236,24 @@ }

const cc = this.callContextStorage.getStore();
return await this.callContextStorage.run({ deadline, metadata: cc?.metadata }, fn);
return await this.callContextStorage.run({ ...cc, deadline }, fn);
}
/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @example
*
* ```ts
* const ctrl = new AbortController();
* setTimeout(() => ctrl.abort(), 10_000);
* // 👇 throws if incomplete by the timeout.
* await conn.withAbortSignal(ctrl.signal, () => client.workflow.execute(myWorkflow, options));
* ```
*/
async withAbortSignal(abortSignal, fn) {
const cc = this.callContextStorage.getStore();
return await this.callContextStorage.run({ ...cc, abortSignal }, fn);
}
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -240,12 +268,11 @@ *

*
*```ts
*const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
*);
*```
* ```ts
* const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
* );
* ```
*/
async withMetadata(metadata, fn) {
const cc = this.callContextStorage.getStore();
metadata = { ...cc?.metadata, ...metadata };
return await this.callContextStorage.run({ metadata, deadline: cc?.deadline }, fn);
return await this.callContextStorage.run({ ...cc, metadata: { ...cc?.metadata, ...metadata } }, fn);
}

@@ -252,0 +279,0 @@ /**

@@ -27,2 +27,10 @@ import { ServiceError as GrpcServiceError } from '@grpc/grpc-js';

/**
* Thrown by the client while waiting on Workflow Update result if Update
* completes with failure.
*/
export declare class WorkflowUpdateFailedError extends Error {
readonly cause: TemporalFailure | undefined;
constructor(message: string, cause: TemporalFailure | undefined);
}
/**
* Thrown the by client while waiting on Workflow execution result if Workflow

@@ -29,0 +37,0 @@ * continues as new.

@@ -9,3 +9,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.isServerErrorResponse = exports.isGrpcServiceError = exports.WorkflowContinuedAsNewError = exports.WorkflowFailedError = exports.ServiceError = void 0;
exports.isServerErrorResponse = exports.isGrpcServiceError = exports.WorkflowContinuedAsNewError = exports.WorkflowUpdateFailedError = exports.WorkflowFailedError = exports.ServiceError = void 0;
const type_helpers_1 = require("@temporalio/common/lib/type-helpers");

@@ -46,2 +46,16 @@ /**

/**
* Thrown by the client while waiting on Workflow Update result if Update
* completes with failure.
*/
let WorkflowUpdateFailedError = class WorkflowUpdateFailedError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
}
};
WorkflowUpdateFailedError = __decorate([
(0, type_helpers_1.SymbolBasedInstanceOfError)('WorkflowUpdateFailedError')
], WorkflowUpdateFailedError);
exports.WorkflowUpdateFailedError = WorkflowUpdateFailedError;
/**
* Thrown the by client while waiting on Workflow execution result if Workflow

@@ -48,0 +62,0 @@ * continues as new.

@@ -10,4 +10,4 @@ /**

import { DescribeWorkflowExecutionResponse, RequestCancelWorkflowExecutionResponse, TerminateWorkflowExecutionResponse, WorkflowExecution } from './types';
import { CompiledWorkflowOptions } from './workflow-options';
export { Next, Headers };
import { CompiledWorkflowOptions, WorkflowUpdateOptions } from './workflow-options';
export { Headers, Next };
/** Input for WorkflowClientInterceptor.start */

@@ -20,2 +20,17 @@ export interface WorkflowStartInput {

}
/** Input for WorkflowClientInterceptor.update */
export interface WorkflowStartUpdateInput {
readonly updateName: string;
readonly args: unknown[];
readonly workflowExecution: WorkflowExecution;
readonly firstExecutionRunId?: string;
readonly headers: Headers;
readonly options: WorkflowUpdateOptions;
}
/** Output for WorkflowClientInterceptor.startUpdate */
export interface WorkflowStartUpdateOutput {
readonly updateId: string;
readonly workflowRunId: string;
readonly outcome?: temporal.api.update.v1.IOutcome;
}
/** Input for WorkflowClientInterceptor.signal */

@@ -72,2 +87,8 @@ export interface WorkflowSignalInput {

/**
* Intercept a service call to updateWorkflowExecution
*
* @experimental Update is an experimental feature.
*/
startUpdate?: (input: WorkflowStartUpdateInput, next: Next<this, 'startUpdate'>) => Promise<WorkflowStartUpdateOutput>;
/**
* Intercept a service call to signalWorkflowExecution

@@ -74,0 +95,0 @@ *

@@ -0,1 +1,2 @@

/// <reference types="node" />
import type * as grpc from '@grpc/grpc-js';

@@ -65,2 +66,3 @@ import type { SearchAttributes } from '@temporalio/common';

metadata?: Metadata;
abortSignal?: AbortSignal;
}

@@ -87,2 +89,9 @@ /**

withMetadata<R>(metadata: Metadata, fn: () => Promise<R>): Promise<R>;
/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*/
withAbortSignal<R>(abortSignal: AbortSignal, fn: () => Promise<R>): Promise<R>;
}
import { status as grpcStatus } from '@grpc/grpc-js';
import { BaseWorkflowHandle, HistoryAndWorkflowId, QueryDefinition, WithWorkflowArgs, Workflow, WorkflowResultType } from '@temporalio/common';
import { BaseWorkflowHandle, HistoryAndWorkflowId, QueryDefinition, UpdateDefinition, WithWorkflowArgs, Workflow, WorkflowResultType } from '@temporalio/common';
import { History } from '@temporalio/common/lib/proto-utils';
import { temporal } from '@temporalio/proto';
import { WorkflowCancelInput, WorkflowClientInterceptor, WorkflowClientInterceptors, WorkflowDescribeInput, WorkflowQueryInput, WorkflowSignalInput, WorkflowSignalWithStartInput, WorkflowStartInput, WorkflowTerminateInput } from './interceptors';
import { WorkflowCancelInput, WorkflowClientInterceptor, WorkflowClientInterceptors, WorkflowDescribeInput, WorkflowQueryInput, WorkflowSignalInput, WorkflowSignalWithStartInput, WorkflowStartInput, WorkflowTerminateInput, WorkflowStartUpdateInput, WorkflowStartUpdateOutput } from './interceptors';
import { DescribeWorkflowExecutionResponse, RequestCancelWorkflowExecutionResponse, TerminateWorkflowExecutionResponse, WorkflowExecution, WorkflowExecutionDescription, WorkflowExecutionInfo, WorkflowService } from './types';
import { WorkflowOptions, WorkflowSignalWithStartOptions, WorkflowStartOptions } from './workflow-options';
import { WorkflowOptions, WorkflowSignalWithStartOptions, WorkflowStartOptions, WorkflowUpdateOptions } from './workflow-options';
import { BaseClient, BaseClientOptions, LoadedWithDefaults } from './base-client';

@@ -15,4 +15,5 @@ /**

* ```ts
* export const incrementSignal = defineSignal('increment');
* export const incrementSignal = defineSignal<[number]>('increment');
* export const getValueQuery = defineQuery<number>('getValue');
* export const incrementAndGetValueUpdate = defineUpdate<number, [number]>('incrementAndGetValue');
* export async function counterWorkflow(initialValue: number): Promise<void>;

@@ -31,5 +32,8 @@ * ```

* await handle.signal(incrementSignal, 2);
* await handle.query(getValueQuery); // 4
* const queryResult = await handle.query(getValueQuery); // 4
* const firstUpdateResult = await handle.executeUpdate(incrementAndGetValueUpdate, { args: [2] }); // 6
* const secondUpdateHandle = await handle.startUpdate(incrementAndGetValueUpdate, { args: [2] });
* const secondUpdateResult = await secondUpdateHandle.result(); // 8
* await handle.cancel();
* await handle.result(); // throws WorkflowExecutionCancelledError
* await handle.result(); // throws a WorkflowFailedError with `cause` set to a CancelledFailure.
* ```

@@ -39,2 +43,50 @@ */

/**
* Start an Update and wait for the result.
*
* @experimental Update is an experimental feature.
*
* @throws {@link WorkflowUpdateFailedError} if Update validation fails or if ApplicationFailure is thrown in the Update handler.
*
* @param def an Update definition as returned from {@link defineUpdate}
* @param options Update arguments
*
* @example
* ```ts
* const updateResult = await handle.executeUpdate(incrementAndGetValueUpdate, { args: [2] });
* ```
*/
executeUpdate<Ret, Args extends [any, ...any[]], Name extends string = string>(def: UpdateDefinition<Ret, Args, Name> | string, options: WorkflowUpdateOptions & {
args: Args;
}): Promise<Ret>;
executeUpdate<Ret, Args extends [], Name extends string = string>(def: UpdateDefinition<Ret, Args, Name> | string, options?: WorkflowUpdateOptions & {
args?: Args;
}): Promise<Ret>;
/**
* Start an Update and receive a handle to the Update.
* The Update validator (if present) is run before the handle is returned.
*
* @experimental Update is an experimental feature.
*
* @throws {@link WorkflowUpdateFailedError} if Update validation fails.
*
* @param def an Update definition as returned from {@link defineUpdate}
* @param options Update arguments
*
* @example
* ```ts
* const updateHandle = await handle.startUpdate(incrementAndGetValueUpdate, { args: [2] });
* const updateResult = await updateHandle.result();
* ```
*/
startUpdate<Ret, Args extends [any, ...any[]], Name extends string = string>(def: UpdateDefinition<Ret, Args, Name> | string, options: WorkflowUpdateOptions & {
args: Args;
}): Promise<WorkflowUpdateHandle<Ret>>;
startUpdate<Ret, Args extends [], Name extends string = string>(def: UpdateDefinition<Ret, Args, Name> | string, options?: WorkflowUpdateOptions & {
args?: Args;
}): Promise<WorkflowUpdateHandle<Ret>>;
/**
* Get a handle to an Update.
*/
getUpdateHandle<Ret>(updateId: string, workflowId: string, options?: GetWorkflowUpdateHandleOptions): WorkflowUpdateHandle<Ret>;
/**
* Query a running or completed Workflow.

@@ -134,2 +186,5 @@ *

}
/**
* Options for {@link WorkflowClient.getHandle}
*/
export interface GetWorkflowHandleOptions extends WorkflowResultOptions {

@@ -171,2 +226,33 @@ /**

/**
* A client-side handle to an Update.
*/
export interface WorkflowUpdateHandle<Ret> {
/**
* The ID of this Update request.
*/
updateId: string;
/**
* The ID of the Workflow being targeted by this Update request.
*/
workflowId: string;
/**
* The ID of the Run of the Workflow being targeted by this Update request.
*/
workflowRunId?: string;
/**
* Return the result of the Update.
* @throws {@link WorkflowUpdateFailedError} if ApplicationFailure is thrown in the Update handler.
*/
result(): Promise<Ret>;
}
/**
* Options for {@link WorkflowHandle.getUpdateHandle}
*/
export interface GetWorkflowUpdateHandleOptions {
/**
* The ID of the Run of the Workflow targeted by the Update.
*/
workflowRunId?: string;
}
/**
* Options for {@link WorkflowClient.list}

@@ -271,2 +357,14 @@ */

/**
* Start the Update.
*
* Used as the final function of the interceptor chain during startUpdate and executeUpdate.
*/
protected _startUpdateHandler(waitForStage: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage, input: WorkflowStartUpdateInput): Promise<WorkflowStartUpdateOutput>;
protected createWorkflowUpdateHandle<Ret>(updateId: string, workflowId: string, options?: GetWorkflowUpdateHandleOptions, outcome?: temporal.api.update.v1.IOutcome): WorkflowUpdateHandle<Ret>;
/**
* Poll Update until a response with an outcome is received; return that outcome.
* This is used directly; no interceptor is available.
*/
protected _pollForUpdateOutcome(updateId: string, workflowExecution: temporal.api.common.v1.IWorkflowExecution): Promise<temporal.api.update.v1.IOutcome>;
/**
* Uses given input to make a signalWorkflowExecution call to the service

@@ -273,0 +371,0 @@ *

@@ -299,2 +299,93 @@ "use strict";

/**
* Start the Update.
*
* Used as the final function of the interceptor chain during startUpdate and executeUpdate.
*/
async _startUpdateHandler(waitForStage, input) {
const updateId = input.options?.updateId ?? (0, uuid_1.v4)();
const req = {
namespace: this.options.namespace,
workflowExecution: input.workflowExecution,
firstExecutionRunId: input.firstExecutionRunId,
waitPolicy: { lifecycleStage: waitForStage },
request: {
meta: {
updateId,
identity: this.options.identity,
},
input: {
header: { fields: input.headers },
name: input.updateName,
args: { payloads: await (0, internal_non_workflow_1.encodeToPayloads)(this.dataConverter, ...input.args) },
},
},
};
let response;
try {
response = await this.workflowService.updateWorkflowExecution(req);
}
catch (err) {
this.rethrowGrpcError(err, 'Workflow Update failed', input.workflowExecution);
}
return {
updateId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowRunId: response.updateRef.workflowExecution.runId,
outcome: response.outcome ?? undefined,
};
}
createWorkflowUpdateHandle(updateId, workflowId, options, outcome) {
const workflowRunId = options?.workflowRunId;
return {
updateId,
workflowId,
workflowRunId,
result: async () => {
const completedOutcome = outcome ?? (await this._pollForUpdateOutcome(updateId, { workflowId, runId: workflowRunId }));
if (completedOutcome.failure) {
throw new errors_1.WorkflowUpdateFailedError('Workflow Update failed', await (0, internal_non_workflow_1.decodeOptionalFailureToOptionalError)(this.dataConverter, completedOutcome.failure));
}
else {
return await (0, internal_non_workflow_1.decodeFromPayloadsAtIndex)(this.dataConverter, 0, completedOutcome.success?.payloads);
}
},
};
}
/**
* Poll Update until a response with an outcome is received; return that outcome.
* This is used directly; no interceptor is available.
*/
async _pollForUpdateOutcome(updateId, workflowExecution) {
const req = {
namespace: this.options.namespace,
updateRef: { workflowExecution, updateId },
identity: this.options.identity,
waitPolicy: {
lifecycleStage: proto_1.temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED,
},
};
// TODO: Users should be able to use client.withDeadline(timestamp) with a
// Date (as opposed to a duration) to control the total amount of time
// allowed for polling. However, this requires a server change such that the
// server swallows the gRPC timeout and instead responds with a well-formed
// PollWorkflowExecutionUpdateResponse, indicating that the requested
// lifecycle stage has not yet been reached at the time of the deadline
// expiry. See https://github.com/temporalio/temporal/issues/4742
// TODO: When temporal#4742 is released, stop catching DEADLINE_EXCEEDED.
for (;;) {
try {
const response = await this.workflowService.pollWorkflowExecutionUpdate(req);
if (response.outcome) {
return response.outcome;
}
}
catch (err) {
if (!((0, errors_1.isGrpcServiceError)(err) && err.code === grpc_js_1.status.DEADLINE_EXCEEDED)) {
throw err;
}
}
}
}
/**
* Uses given input to make a signalWorkflowExecution call to the service

@@ -347,2 +438,3 @@ *

workflowTaskTimeout: options.workflowTaskTimeout,
workflowStartDelay: options.startDelay,
retryPolicy: options.retry ? (0, common_1.compileRetryPolicy)(options.retry) : undefined,

@@ -391,2 +483,3 @@ memo: options.memo ? { fields: await (0, internal_non_workflow_1.encodeMapToPayloads)(this.dataConverter, options.memo) } : undefined,

workflowTaskTimeout: opts.workflowTaskTimeout,
workflowStartDelay: opts.startDelay,
retryPolicy: opts.retry ? (0, common_1.compileRetryPolicy)(opts.retry) : undefined,

@@ -473,2 +566,19 @@ memo: opts.memo ? { fields: await (0, internal_non_workflow_1.encodeMapToPayloads)(this.dataConverter, opts.memo) } : undefined,

_createWorkflowHandle({ workflowId, runId, firstExecutionRunId, interceptors, runIdForResult, ...resultOptions }) {
// TODO (dan): Convert to class with this as a protected method
const _startUpdate = async (def, waitForStage, options) => {
const next = this._startUpdateHandler.bind(this, waitForStage);
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'startUpdate', next);
const { args, ...opts } = options ?? {};
const input = {
workflowExecution: { workflowId, runId },
firstExecutionRunId,
updateName: typeof def === 'string' ? def : def.name,
args: args ?? [],
waitForStage,
headers: {},
options: opts,
};
const output = await fn(input);
return this.createWorkflowUpdateHandle(output.updateId, input.workflowExecution.workflowId, { workflowRunId: output.workflowRunId }, output.outcome);
};
return {

@@ -482,3 +592,3 @@ client: this,

const next = this.client._terminateWorkflowHandler.bind(this.client);
const fn = interceptors.length ? (0, interceptors_1.composeInterceptors)(interceptors, 'terminate', next) : next;
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'terminate', next);
return await fn({

@@ -492,3 +602,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._cancelWorkflowHandler.bind(this.client);
const fn = interceptors.length ? (0, interceptors_1.composeInterceptors)(interceptors, 'cancel', next) : next;
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'cancel', next);
return await fn({

@@ -501,3 +611,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._describeWorkflowHandler.bind(this.client);
const fn = interceptors.length ? (0, interceptors_1.composeInterceptors)(interceptors, 'describe', next) : next;
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'describe', next);
const raw = await fn({

@@ -528,5 +638,17 @@ workflowExecution: { workflowId, runId },

},
async startUpdate(def, options) {
return await _startUpdate(def, proto_1.temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED, options);
},
async executeUpdate(def, options) {
const handle = await _startUpdate(def, proto_1.temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED, options);
return await handle.result();
},
getUpdateHandle(updateId, workflowId, options) {
return this.client.createWorkflowUpdateHandle(updateId, workflowId, options);
},
async signal(def, ...args) {
const next = this.client._signalWorkflowHandler.bind(this.client);
const fn = interceptors.length ? (0, interceptors_1.composeInterceptors)(interceptors, 'signal', next) : next;
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'signal', next);
await fn({

@@ -541,3 +663,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._queryWorkflowHandler.bind(this.client);
const fn = interceptors.length ? (0, interceptors_1.composeInterceptors)(interceptors, 'query', next) : next;
const fn = (0, interceptors_1.composeInterceptors)(interceptors, 'query', next);
return fn({

@@ -544,0 +666,0 @@ workflowExecution: { workflowId, runId },

@@ -1,2 +0,5 @@

import { CommonWorkflowOptions, SignalDefinition, WithCompiledWorkflowOptions, WithWorkflowArgs, Workflow } from '@temporalio/common';
import { CommonWorkflowOptions, SignalDefinition, WithWorkflowArgs, Workflow } from '@temporalio/common';
import { Duration } from '@temporalio/common/lib/time';
import { Replace } from '@temporalio/common/lib/type-helpers';
import { google } from '@temporalio/proto';
export * from '@temporalio/common/lib/workflow-options';

@@ -29,3 +32,19 @@ export interface CompiledWorkflowOptions extends WithCompiledWorkflowOptions<WorkflowOptions> {

followRuns?: boolean;
/**
* Amount of time to wait before starting the workflow.
*
* @experimental
*/
startDelay?: Duration;
}
export type WithCompiledWorkflowOptions<T extends WorkflowOptions> = Replace<T, {
workflowExecutionTimeout?: google.protobuf.IDuration;
workflowRunTimeout?: google.protobuf.IDuration;
workflowTaskTimeout?: google.protobuf.IDuration;
startDelay?: google.protobuf.IDuration;
}>;
export declare function compileWorkflowOptions<T extends WorkflowOptions>(options: T): WithCompiledWorkflowOptions<T>;
export interface WorkflowUpdateOptions {
readonly updateId?: string;
}
export type WorkflowSignalWithStartOptions<SignalArgs extends any[] = []> = SignalArgs extends [any, ...any[]] ? WorkflowSignalWithStartOptionsWithArgs<SignalArgs> : WorkflowSignalWithStartOptionsWithoutArgs<SignalArgs>;

@@ -32,0 +51,0 @@ export interface WorkflowSignalWithStartOptionsWithoutArgs<SignalArgs extends any[]> extends WorkflowOptions {

@@ -17,3 +17,16 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.compileWorkflowOptions = void 0;
const time_1 = require("@temporalio/common/lib/time");
__exportStar(require("@temporalio/common/lib/workflow-options"), exports);
function compileWorkflowOptions(options) {
const { workflowExecutionTimeout, workflowRunTimeout, workflowTaskTimeout, startDelay, ...rest } = options;
return {
...rest,
workflowExecutionTimeout: (0, time_1.msOptionalToTs)(workflowExecutionTimeout),
workflowRunTimeout: (0, time_1.msOptionalToTs)(workflowRunTimeout),
workflowTaskTimeout: (0, time_1.msOptionalToTs)(workflowTaskTimeout),
startDelay: (0, time_1.msOptionalToTs)(startDelay),
};
}
exports.compileWorkflowOptions = compileWorkflowOptions;
//# sourceMappingURL=workflow-options.js.map
{
"name": "@temporalio/client",
"version": "1.8.6",
"version": "1.9.0-rc.0",
"description": "Temporal.io SDK Client sub-package",

@@ -17,4 +17,4 @@ "main": "lib/index.js",

"@grpc/grpc-js": "~1.7.3",
"@temporalio/common": "1.8.6",
"@temporalio/proto": "1.8.6",
"@temporalio/common": "1.9.0-rc.0",
"@temporalio/proto": "1.9.0-rc.0",
"abort-controller": "^3.0.0",

@@ -43,3 +43,3 @@ "long": "^5.2.0",

],
"gitHead": "1e95cf92ec5e6efffb7aedb064ea46be05df0c14"
"gitHead": "ca3e508e62de02b2c9bb40d0d889003cebba282d"
}

@@ -0,1 +1,3 @@

// Keep this around until we drop support for Node 14.
import 'abort-controller/polyfill'; // eslint-disable-line import/no-unassigned-import
import os from 'node:os';

@@ -71,2 +73,14 @@ import { DataConverter, LoadedDataConverter } from '@temporalio/common';

/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing service requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @see {@link Connection.withAbortSignal}
*/
async withAbortSignal<R>(abortSignal: AbortSignal, fn: () => Promise<R>): Promise<R> {
return await this.connection.withAbortSignal(abortSignal, fn);
}
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -73,0 +87,0 @@ *

@@ -0,1 +1,3 @@

// Keep this around until we drop support for Node 14.
import 'abort-controller/polyfill'; // eslint-disable-line import/no-unassigned-import
import { AsyncLocalStorage } from 'node:async_hooks';

@@ -24,7 +26,11 @@ import * as grpc from '@grpc/grpc-js';

/**
* TLS configuration.
* Pass a falsy value to use a non-encrypted connection or `true` or `{}` to
* connect with TLS without any customization.
* TLS configuration. Pass a falsy value to use a non-encrypted connection,
* or `true` or `{}` to connect with TLS without any customization.
*
* For advanced scenario, a prebuilt {@link grpc.ChannelCredentials} object
* may instead be specified using the {@link credentials} property.
*
* Either {@link credentials} or this may be specified for configuring TLS
*
* @default TLS is disabled
*/

@@ -34,4 +40,12 @@ tls?: TLSConfig | boolean | null;

/**
* Channel credentials, create using the factory methods defined {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
* gRPC channel credentials.
*
* `ChannelCredentials` are things like SSL credentials that can be used to secure a connection.
* There may be only one `ChannelCredentials`. They can be created using some of the factory
* methods defined {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
*
* Specifying a prebuilt `ChannelCredentials` should only be required for advanced use cases.
* For simple TLS use cases, using the {@link tls} property is recommended. To register
* `CallCredentials` (eg. metadata-based authentication), use the {@link callCredentials} property.
*
* Either {@link tls} or this may be specified for configuring TLS

@@ -42,2 +56,16 @@ */

/**
* gRPC call credentials.
*
* `CallCredentials` generaly modify metadata; they can be attached to a connection to affect all method
* calls made using that connection. They can be created using some of the factory methods defined
* {@link https://grpc.github.io/grpc/node/grpc.credentials.html | here}
*
* If `callCredentials` are specified, they will be composed with channel credentials
* (either the one created implicitely by using the {@link tls} option, or the one specified
* explicitly through {@link credentials}). Notice that gRPC doesn't allow registering
* `callCredentials` on insecure connections.
*/
callCredentials?: grpc.CallCredentials[];
/**
* GRPC Channel arguments

@@ -90,3 +118,5 @@ *

export type ConnectionOptionsWithDefaults = Required<Omit<ConnectionOptions, 'tls' | 'connectTimeout'>> & {
export type ConnectionOptionsWithDefaults = Required<
Omit<ConnectionOptions, 'tls' | 'connectTimeout' | 'callCredentials'>
> & {
connectTimeoutMs: number;

@@ -121,3 +151,3 @@ };

function normalizeGRPCConfig(options?: ConnectionOptions): ConnectionOptions {
const { tls: tlsFromConfig, credentials, ...rest } = options || {};
const { tls: tlsFromConfig, credentials, callCredentials, ...rest } = options || {};
if (rest.address) {

@@ -136,6 +166,5 @@ // eslint-disable-next-line prefer-const

...rest,
credentials: grpc.credentials.createSsl(
tls.serverRootCACertificate,
tls.clientCertPair?.key,
tls.clientCertPair?.crt
credentials: grpc.credentials.combineChannelCredentials(
grpc.credentials.createSsl(tls.serverRootCACertificate, tls.clientCertPair?.key, tls.clientCertPair?.crt),
...(callCredentials ?? [])
),

@@ -153,3 +182,9 @@ channelArgs: {

} else {
return rest;
return {
...rest,
credentials: grpc.credentials.combineChannelCredentials(
credentials ?? grpc.credentials.createInsecure(),
...(callCredentials ?? [])
),
};
}

@@ -215,2 +250,8 @@ }

* {@link https://github.com/temporalio/api/blob/master/temporal/api/operatorservice/v1/service.proto | Operator service}
*
* The Operator Service API defines how Temporal SDKs and other clients interact with the Temporal
* server to perform administrative functions like registering a search attribute or a namespace.
*
* This Service API is NOT compatible with Temporal Cloud. Attempt to use it against a Temporal
* Cloud namespace will result in gRPC `unauthorized` error.
*/

@@ -347,3 +388,3 @@ public readonly operatorService: OperatorService;

const metadataContainer = new grpc.Metadata();
const { metadata, deadline } = callContextStorage.getStore() ?? {};
const { metadata, deadline, abortSignal } = callContextStorage.getStore() ?? {};
for (const [k, v] of Object.entries(staticMetadata)) {

@@ -357,3 +398,3 @@ metadataContainer.set(k, v);

}
return client.makeUnaryRequest(
const call = client.makeUnaryRequest(
`/${serviceName}/${method.name}`,

@@ -367,2 +408,7 @@ (arg: any) => arg,

);
if (abortSignal != null) {
abortSignal.addEventListener('abort', () => call.cancel());
}
return call;
};

@@ -378,6 +424,26 @@ }

const cc = this.callContextStorage.getStore();
return await this.callContextStorage.run({ deadline, metadata: cc?.metadata }, fn);
return await this.callContextStorage.run({ ...cc, deadline }, fn);
}
/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*
* @example
*
* ```ts
* const ctrl = new AbortController();
* setTimeout(() => ctrl.abort(), 10_000);
* // 👇 throws if incomplete by the timeout.
* await conn.withAbortSignal(ctrl.signal, () => client.workflow.execute(myWorkflow, options));
* ```
*/
async withAbortSignal<ReturnType>(abortSignal: AbortSignal, fn: () => Promise<ReturnType>): Promise<ReturnType> {
const cc = this.callContextStorage.getStore();
return await this.callContextStorage.run({ ...cc, abortSignal }, fn);
}
/**
* Set metadata for any service requests executed in `fn`'s scope.

@@ -392,12 +458,11 @@ *

*
*```ts
*const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
*);
*```
* ```ts
* const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () =>
* conn.withMetadata({ otherKey: 'set' }, () => client.start(options)))
* );
* ```
*/
async withMetadata<ReturnType>(metadata: Metadata, fn: () => Promise<ReturnType>): Promise<ReturnType> {
const cc = this.callContextStorage.getStore();
metadata = { ...cc?.metadata, ...metadata };
return await this.callContextStorage.run({ metadata, deadline: cc?.deadline }, fn);
return await this.callContextStorage.run({ ...cc, metadata: { ...cc?.metadata, ...metadata } }, fn);
}

@@ -404,0 +469,0 @@

@@ -39,2 +39,13 @@ import { ServiceError as GrpcServiceError } from '@grpc/grpc-js';

/**
* Thrown by the client while waiting on Workflow Update result if Update
* completes with failure.
*/
@SymbolBasedInstanceOfError('WorkflowUpdateFailedError')
export class WorkflowUpdateFailedError extends Error {
public constructor(message: string, public readonly cause: TemporalFailure | undefined) {
super(message);
}
}
/**
* Thrown the by client while waiting on Workflow execution result if Workflow

@@ -41,0 +52,0 @@ * continues as new.

@@ -16,5 +16,5 @@ /**

} from './types';
import { CompiledWorkflowOptions } from './workflow-options';
import { CompiledWorkflowOptions, WorkflowUpdateOptions } from './workflow-options';
export { Next, Headers };
export { Headers, Next };

@@ -29,2 +29,19 @@ /** Input for WorkflowClientInterceptor.start */

/** Input for WorkflowClientInterceptor.update */
export interface WorkflowStartUpdateInput {
readonly updateName: string;
readonly args: unknown[];
readonly workflowExecution: WorkflowExecution;
readonly firstExecutionRunId?: string;
readonly headers: Headers;
readonly options: WorkflowUpdateOptions;
}
/** Output for WorkflowClientInterceptor.startUpdate */
export interface WorkflowStartUpdateOutput {
readonly updateId: string;
readonly workflowRunId: string;
readonly outcome?: temporal.api.update.v1.IOutcome;
}
/** Input for WorkflowClientInterceptor.signal */

@@ -87,2 +104,11 @@ export interface WorkflowSignalInput {

/**
* Intercept a service call to updateWorkflowExecution
*
* @experimental Update is an experimental feature.
*/
startUpdate?: (
input: WorkflowStartUpdateInput,
next: Next<this, 'startUpdate'>
) => Promise<WorkflowStartUpdateOutput>;
/**
* Intercept a service call to signalWorkflowExecution

@@ -89,0 +115,0 @@ *

@@ -85,2 +85,4 @@ import type * as grpc from '@grpc/grpc-js';

metadata?: Metadata;
abortSignal?: AbortSignal;
}

@@ -109,2 +111,10 @@

withMetadata<R>(metadata: Metadata, fn: () => Promise<R>): Promise<R>;
/**
* Set an {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal | `AbortSignal`} that, when aborted,
* cancels any ongoing requests executed in `fn`'s scope.
*
* @returns value returned from `fn`
*/
withAbortSignal<R>(abortSignal: AbortSignal, fn: () => Promise<R>): Promise<R>;
}

@@ -13,2 +13,3 @@ import { status as grpcStatus } from '@grpc/grpc-js';

SignalDefinition,
UpdateDefinition,
TerminatedFailure,

@@ -36,4 +37,10 @@ TimeoutFailure,

import { temporal } from '@temporalio/proto';
import { ServiceError, WorkflowContinuedAsNewError, WorkflowFailedError, isGrpcServiceError } from './errors';
import {
ServiceError,
WorkflowContinuedAsNewError,
WorkflowFailedError,
WorkflowUpdateFailedError,
isGrpcServiceError,
} from './errors';
import {
WorkflowCancelInput,

@@ -48,2 +55,4 @@ WorkflowClientInterceptor,

WorkflowTerminateInput,
WorkflowStartUpdateInput,
WorkflowStartUpdateOutput,
} from './interceptors';

@@ -66,2 +75,3 @@ import {

WorkflowStartOptions,
WorkflowUpdateOptions,
} from './workflow-options';

@@ -84,4 +94,5 @@ import { executionInfoFromRaw, rethrowKnownErrorTypes } from './helpers';

* ```ts
* export const incrementSignal = defineSignal('increment');
* export const incrementSignal = defineSignal<[number]>('increment');
* export const getValueQuery = defineQuery<number>('getValue');
* export const incrementAndGetValueUpdate = defineUpdate<number, [number]>('incrementAndGetValue');
* export async function counterWorkflow(initialValue: number): Promise<void>;

@@ -100,5 +111,8 @@ * ```

* await handle.signal(incrementSignal, 2);
* await handle.query(getValueQuery); // 4
* const queryResult = await handle.query(getValueQuery); // 4
* const firstUpdateResult = await handle.executeUpdate(incrementAndGetValueUpdate, { args: [2] }); // 6
* const secondUpdateHandle = await handle.startUpdate(incrementAndGetValueUpdate, { args: [2] });
* const secondUpdateResult = await secondUpdateHandle.result(); // 8
* await handle.cancel();
* await handle.result(); // throws WorkflowExecutionCancelledError
* await handle.result(); // throws a WorkflowFailedError with `cause` set to a CancelledFailure.
* ```

@@ -108,2 +122,63 @@ */

/**
* Start an Update and wait for the result.
*
* @experimental Update is an experimental feature.
*
* @throws {@link WorkflowUpdateFailedError} if Update validation fails or if ApplicationFailure is thrown in the Update handler.
*
* @param def an Update definition as returned from {@link defineUpdate}
* @param options Update arguments
*
* @example
* ```ts
* const updateResult = await handle.executeUpdate(incrementAndGetValueUpdate, { args: [2] });
* ```
*/
executeUpdate<Ret, Args extends [any, ...any[]], Name extends string = string>(
def: UpdateDefinition<Ret, Args, Name> | string,
options: WorkflowUpdateOptions & { args: Args }
): Promise<Ret>;
executeUpdate<Ret, Args extends [], Name extends string = string>(
def: UpdateDefinition<Ret, Args, Name> | string,
options?: WorkflowUpdateOptions & { args?: Args }
): Promise<Ret>;
/**
* Start an Update and receive a handle to the Update.
* The Update validator (if present) is run before the handle is returned.
*
* @experimental Update is an experimental feature.
*
* @throws {@link WorkflowUpdateFailedError} if Update validation fails.
*
* @param def an Update definition as returned from {@link defineUpdate}
* @param options Update arguments
*
* @example
* ```ts
* const updateHandle = await handle.startUpdate(incrementAndGetValueUpdate, { args: [2] });
* const updateResult = await updateHandle.result();
* ```
*/
startUpdate<Ret, Args extends [any, ...any[]], Name extends string = string>(
def: UpdateDefinition<Ret, Args, Name> | string,
options: WorkflowUpdateOptions & { args: Args }
): Promise<WorkflowUpdateHandle<Ret>>;
startUpdate<Ret, Args extends [], Name extends string = string>(
def: UpdateDefinition<Ret, Args, Name> | string,
options?: WorkflowUpdateOptions & { args?: Args }
): Promise<WorkflowUpdateHandle<Ret>>;
/**
* Get a handle to an Update.
*/
getUpdateHandle<Ret>(
updateId: string,
workflowId: string,
options?: GetWorkflowUpdateHandleOptions
): WorkflowUpdateHandle<Ret>;
/**
* Query a running or completed Workflow.

@@ -240,2 +315,5 @@ *

/**
* Options for {@link WorkflowClient.getHandle}
*/
export interface GetWorkflowHandleOptions extends WorkflowResultOptions {

@@ -280,2 +358,38 @@ /**

/**
* A client-side handle to an Update.
*/
export interface WorkflowUpdateHandle<Ret> {
/**
* The ID of this Update request.
*/
updateId: string;
/**
* The ID of the Workflow being targeted by this Update request.
*/
workflowId: string;
/**
* The ID of the Run of the Workflow being targeted by this Update request.
*/
workflowRunId?: string;
/**
* Return the result of the Update.
* @throws {@link WorkflowUpdateFailedError} if ApplicationFailure is thrown in the Update handler.
*/
result(): Promise<Ret>;
}
/**
* Options for {@link WorkflowHandle.getUpdateHandle}
*/
export interface GetWorkflowUpdateHandleOptions {
/**
* The ID of the Run of the Workflow targeted by the Update.
*/
workflowRunId?: string;
}
/**
* Options for {@link WorkflowClient.list}

@@ -653,2 +767,112 @@ */

/**
* Start the Update.
*
* Used as the final function of the interceptor chain during startUpdate and executeUpdate.
*/
protected async _startUpdateHandler(
waitForStage: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage,
input: WorkflowStartUpdateInput
): Promise<WorkflowStartUpdateOutput> {
const updateId = input.options?.updateId ?? uuid4();
const req: temporal.api.workflowservice.v1.IUpdateWorkflowExecutionRequest = {
namespace: this.options.namespace,
workflowExecution: input.workflowExecution,
firstExecutionRunId: input.firstExecutionRunId,
waitPolicy: { lifecycleStage: waitForStage },
request: {
meta: {
updateId,
identity: this.options.identity,
},
input: {
header: { fields: input.headers },
name: input.updateName,
args: { payloads: await encodeToPayloads(this.dataConverter, ...input.args) },
},
},
};
let response: temporal.api.workflowservice.v1.UpdateWorkflowExecutionResponse;
try {
response = await this.workflowService.updateWorkflowExecution(req);
} catch (err) {
this.rethrowGrpcError(err, 'Workflow Update failed', input.workflowExecution);
}
return {
updateId,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
workflowRunId: response.updateRef!.workflowExecution!.runId!,
outcome: response.outcome ?? undefined,
};
}
protected createWorkflowUpdateHandle<Ret>(
updateId: string,
workflowId: string,
options?: GetWorkflowUpdateHandleOptions,
outcome?: temporal.api.update.v1.IOutcome
): WorkflowUpdateHandle<Ret> {
const workflowRunId = options?.workflowRunId;
return {
updateId,
workflowId,
workflowRunId,
result: async () => {
const completedOutcome =
outcome ?? (await this._pollForUpdateOutcome(updateId, { workflowId, runId: workflowRunId }));
if (completedOutcome.failure) {
throw new WorkflowUpdateFailedError(
'Workflow Update failed',
await decodeOptionalFailureToOptionalError(this.dataConverter, completedOutcome.failure)
);
} else {
return await decodeFromPayloadsAtIndex<Ret>(this.dataConverter, 0, completedOutcome.success?.payloads);
}
},
};
}
/**
* Poll Update until a response with an outcome is received; return that outcome.
* This is used directly; no interceptor is available.
*/
protected async _pollForUpdateOutcome(
updateId: string,
workflowExecution: temporal.api.common.v1.IWorkflowExecution
): Promise<temporal.api.update.v1.IOutcome> {
const req: temporal.api.workflowservice.v1.IPollWorkflowExecutionUpdateRequest = {
namespace: this.options.namespace,
updateRef: { workflowExecution, updateId },
identity: this.options.identity,
waitPolicy: {
lifecycleStage:
temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED,
},
};
// TODO: Users should be able to use client.withDeadline(timestamp) with a
// Date (as opposed to a duration) to control the total amount of time
// allowed for polling. However, this requires a server change such that the
// server swallows the gRPC timeout and instead responds with a well-formed
// PollWorkflowExecutionUpdateResponse, indicating that the requested
// lifecycle stage has not yet been reached at the time of the deadline
// expiry. See https://github.com/temporalio/temporal/issues/4742
// TODO: When temporal#4742 is released, stop catching DEADLINE_EXCEEDED.
for (;;) {
try {
const response = await this.workflowService.pollWorkflowExecutionUpdate(req);
if (response.outcome) {
return response.outcome;
}
} catch (err) {
if (!(isGrpcServiceError(err) && err.code === grpcStatus.DEADLINE_EXCEEDED)) {
throw err;
}
}
}
}
/**
* Uses given input to make a signalWorkflowExecution call to the service

@@ -701,2 +925,3 @@ *

workflowTaskTimeout: options.workflowTaskTimeout,
workflowStartDelay: options.startDelay,
retryPolicy: options.retry ? compileRetryPolicy(options.retry) : undefined,

@@ -749,2 +974,3 @@ memo: options.memo ? { fields: await encodeMapToPayloads(this.dataConverter, options.memo) } : undefined,

workflowTaskTimeout: opts.workflowTaskTimeout,
workflowStartDelay: opts.startDelay,
retryPolicy: opts.retry ? compileRetryPolicy(opts.retry) : undefined,

@@ -844,2 +1070,29 @@ memo: opts.memo ? { fields: await encodeMapToPayloads(this.dataConverter, opts.memo) } : undefined,

}: WorkflowHandleOptions): WorkflowHandle<T> {
// TODO (dan): Convert to class with this as a protected method
const _startUpdate = async <Ret, Args extends unknown[]>(
def: UpdateDefinition<Ret, Args> | string,
waitForStage: temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage,
options?: WorkflowUpdateOptions & { args?: Args }
): Promise<WorkflowUpdateHandle<Ret>> => {
const next = this._startUpdateHandler.bind(this, waitForStage);
const fn = composeInterceptors(interceptors, 'startUpdate', next);
const { args, ...opts } = options ?? {};
const input = {
workflowExecution: { workflowId, runId },
firstExecutionRunId,
updateName: typeof def === 'string' ? def : def.name,
args: args ?? [],
waitForStage,
headers: {},
options: opts,
};
const output = await fn(input);
return this.createWorkflowUpdateHandle<Ret>(
output.updateId,
input.workflowExecution.workflowId,
{ workflowRunId: output.workflowRunId },
output.outcome
);
};
return {

@@ -853,3 +1106,3 @@ client: this,

const next = this.client._terminateWorkflowHandler.bind(this.client);
const fn = interceptors.length ? composeInterceptors(interceptors, 'terminate', next) : next;
const fn = composeInterceptors(interceptors, 'terminate', next);
return await fn({

@@ -863,3 +1116,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._cancelWorkflowHandler.bind(this.client);
const fn = interceptors.length ? composeInterceptors(interceptors, 'cancel', next) : next;
const fn = composeInterceptors(interceptors, 'cancel', next);
return await fn({

@@ -872,3 +1125,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._describeWorkflowHandler.bind(this.client);
const fn = interceptors.length ? composeInterceptors(interceptors, 'describe', next) : next;
const fn = composeInterceptors(interceptors, 'describe', next);
const raw = await fn({

@@ -899,5 +1152,35 @@ workflowExecution: { workflowId, runId },

},
async startUpdate<Ret, Args extends any[]>(
def: UpdateDefinition<Ret, Args> | string,
options?: WorkflowUpdateOptions & { args?: Args }
): Promise<WorkflowUpdateHandle<Ret>> {
return await _startUpdate(
def,
temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED,
options
);
},
async executeUpdate<Ret, Args extends any[]>(
def: UpdateDefinition<Ret, Args> | string,
options?: WorkflowUpdateOptions & { args?: Args }
): Promise<Ret> {
const handle = await _startUpdate(
def,
temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage
.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED,
options
);
return await handle.result();
},
getUpdateHandle<Ret>(
updateId: string,
workflowId: string,
options?: GetWorkflowUpdateHandleOptions
): WorkflowUpdateHandle<Ret> {
return this.client.createWorkflowUpdateHandle(updateId, workflowId, options);
},
async signal<Args extends any[]>(def: SignalDefinition<Args> | string, ...args: Args): Promise<void> {
const next = this.client._signalWorkflowHandler.bind(this.client);
const fn = interceptors.length ? composeInterceptors(interceptors, 'signal', next) : next;
const fn = composeInterceptors(interceptors, 'signal', next);
await fn({

@@ -912,3 +1195,3 @@ workflowExecution: { workflowId, runId },

const next = this.client._queryWorkflowHandler.bind(this.client);
const fn = interceptors.length ? composeInterceptors(interceptors, 'query', next) : next;
const fn = composeInterceptors(interceptors, 'query', next);
return fn({

@@ -915,0 +1198,0 @@ workflowExecution: { workflowId, runId },

@@ -1,8 +0,5 @@

import {
CommonWorkflowOptions,
SignalDefinition,
WithCompiledWorkflowOptions,
WithWorkflowArgs,
Workflow,
} from '@temporalio/common';
import { CommonWorkflowOptions, SignalDefinition, WithWorkflowArgs, Workflow } from '@temporalio/common';
import { Duration, msOptionalToTs } from '@temporalio/common/lib/time';
import { Replace } from '@temporalio/common/lib/type-helpers';
import { google } from '@temporalio/proto';

@@ -40,4 +37,37 @@ export * from '@temporalio/common/lib/workflow-options';

followRuns?: boolean;
/**
* Amount of time to wait before starting the workflow.
*
* @experimental
*/
startDelay?: Duration;
}
export type WithCompiledWorkflowOptions<T extends WorkflowOptions> = Replace<
T,
{
workflowExecutionTimeout?: google.protobuf.IDuration;
workflowRunTimeout?: google.protobuf.IDuration;
workflowTaskTimeout?: google.protobuf.IDuration;
startDelay?: google.protobuf.IDuration;
}
>;
export function compileWorkflowOptions<T extends WorkflowOptions>(options: T): WithCompiledWorkflowOptions<T> {
const { workflowExecutionTimeout, workflowRunTimeout, workflowTaskTimeout, startDelay, ...rest } = options;
return {
...rest,
workflowExecutionTimeout: msOptionalToTs(workflowExecutionTimeout),
workflowRunTimeout: msOptionalToTs(workflowRunTimeout),
workflowTaskTimeout: msOptionalToTs(workflowTaskTimeout),
startDelay: msOptionalToTs(startDelay),
};
}
export interface WorkflowUpdateOptions {
readonly updateId?: string;
}
export type WorkflowSignalWithStartOptions<SignalArgs extends any[] = []> = SignalArgs extends [any, ...any[]]

@@ -44,0 +74,0 @@ ? WorkflowSignalWithStartOptionsWithArgs<SignalArgs>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc