UNPKG

@temporalio/worker

Version:
253 lines 9.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InternalNativeConnection = exports.NativeConnection = void 0; exports.extractNativeClient = extractNativeClient; exports.extractReferenceHolders = extractReferenceHolders; const node_async_hooks_1 = require("node:async_hooks"); const common_1 = require("@temporalio/common"); const core_bridge_1 = require("@temporalio/core-bridge"); const client_1 = require("@temporalio/client"); const errors_1 = require("./errors"); const runtime_1 = require("./runtime"); /** * A Native Connection object that delegates calls to the Rust Core binary extension. * * A Worker must use this class to connect to the server. * * This class can be used to power `@temporalio/client`'s Client objects. */ class NativeConnection { runtime; nativeClient; /** * referenceHolders is used internally by the framework, it can be accessed with `extractReferenceHolders` (below) */ referenceHolders = new Set(); workflowService; callContextStorage = new node_async_hooks_1.AsyncLocalStorage(); /** * nativeClient is intentionally left private, framework code can access it with `extractNativeClient` (below) */ constructor(runtime, nativeClient) { this.runtime = runtime; this.nativeClient = nativeClient; this.workflowService = client_1.WorkflowService.create(this.sendRequest.bind(this), false, false); } /** * No-op. This class can only be created via eager connection. */ async ensureConnected() { } sendRequest(method, requestData, callback) { if (!isProtoMethod(method)) { throw new TypeError(`Invalid request method, expected a proto.Method instance: ${method.name}`); } const { resolvedResponseType } = method; if (resolvedResponseType == null) { throw new TypeError(`Invalid request method: ${method.name}`); } // TODO: add support for abortSignal const ctx = this.callContextStorage.getStore() ?? {}; const metadata = ctx.metadata != null ? Object.fromEntries(Object.entries(ctx.metadata).map(([k, v]) => [k, v.toString()])) : {}; const req = { rpc: method.name, req: requestData, retry: true, metadata, timeout: ctx.deadline ? getRelativeTimeout(ctx.deadline) : null, }; core_bridge_1.native.clientSendRequest(this.nativeClient, req).then((res) => { callback(null, resolvedResponseType.decode(Buffer.from(res))); }, (err) => { callback(err); }); } /** * Set a deadline for any service requests executed in `fn`'s scope. * * The deadline is a point in time after which any pending gRPC request will be considered as failed; * this will locally result in the request call throwing a {@link grpc.ServiceError|ServiceError} * with code {@link grpc.status.DEADLINE_EXCEEDED|DEADLINE_EXCEEDED}; see {@link isGrpcDeadlineError}. * * It is stronly recommended to explicitly set deadlines. If no deadline is set, then it is * possible for the client to end up waiting forever for a response. * * @param deadline a point in time after which the request will be considered as failed; either a * Date object, or a number of milliseconds since the Unix epoch (UTC). * @returns the value returned from `fn` * * @see https://grpc.io/docs/guides/deadlines/ */ async withDeadline(deadline, fn) { const cc = this.callContextStorage.getStore(); return await this.callContextStorage.run({ ...cc, deadline }, fn); } /** * Set metadata for any service requests executed in `fn`'s scope. * * The provided metadata is merged on top of any existing metadata in current scope, including metadata provided in * {@link NativeConnectionOptions.metadata}. * * @returns value returned from `fn` * * @example * * ```ts * const workflowHandle = await conn.withMetadata({ apiKey: 'secret' }, () => * conn.withMetadata({ otherKey: 'set' }, () => client.start(options))) * ); * ``` */ async withMetadata(metadata, fn) { const cc = this.callContextStorage.getStore(); return await this.callContextStorage.run({ ...cc, metadata: { ...cc?.metadata, ...metadata }, }, fn); } /** * Set an {@link AbortSignal} that, when aborted, cancels any ongoing service requests executed in * `fn`'s scope. This will locally result in the request call throwing a {@link grpc.ServiceError|ServiceError} * with code {@link grpc.status.CANCELLED|CANCELLED}; see {@link isGrpcCancelledError}. * * This method is only a convenience wrapper around {@link NativeConnection.withAbortSignal}. * * @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)); * ``` * * @returns value returned from `fn` * * @see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal */ async withAbortSignal(abortSignal, fn) { const cc = this.callContextStorage.getStore(); return await this.callContextStorage.run({ ...cc, abortSignal }, fn); } /** * @deprecated use `connect` instead */ static async create(options) { try { const runtime = runtime_1.Runtime.instance(); const client = await runtime.createNativeClient(options); return new this(runtime, client); } catch (err) { if (err instanceof errors_1.TransportError) { throw new errors_1.TransportError(err.message); } throw err; } } /** * Eagerly connect to the Temporal server and return a NativeConnection instance */ static async connect(options) { try { const runtime = runtime_1.Runtime.instance(); const client = await runtime.createNativeClient(options); return new this(runtime, client); } catch (err) { if (err instanceof errors_1.TransportError) { throw new errors_1.TransportError(err.message); } throw err; } } /** * Close this connection. * * Make sure any Workers using this connection are stopped before calling * this method or it will throw an {@link IllegalStateError} */ async close() { if (this.referenceHolders.size > 0) { throw new common_1.IllegalStateError('Cannot close connection while Workers hold a reference to it'); } await this.runtime.closeNativeClient(this.nativeClient); } /** * Mapping of gRPC metadata (HTTP headers) to send with each request to the server. * * Use {@link NativeConnectionOptions.metadata} to set the initial metadata for client creation. */ async setMetadata(metadata) { core_bridge_1.native.clientUpdateHeaders(this.nativeClient, metadata); } /** * Update the API key for this client. This is only set if `metadata` doesn't already have an * "authorization" key. * * Use {@link NativeConnectionOptions.apiKey} to set the initial metadata for client creation. */ async setApiKey(apiKey) { core_bridge_1.native.clientUpdateApiKey(this.nativeClient, apiKey); } } exports.NativeConnection = NativeConnection; /** * Extract the private native client instance from a `NativeConnection` instance. * * Only meant to be used by the framework. */ function extractNativeClient(conn) { return conn.nativeClient; } /** * Extract the private referenceHolders set from a `NativeConnection` instance. * * Only meant to be used by the framework. */ function extractReferenceHolders(conn) { return conn.referenceHolders; } /** * Internal class used when a Worker directly instantiates a connection with no external references. * * This class is only used as a "marker" during Worker shutdown to decide whether to close the connection. */ class InternalNativeConnection extends NativeConnection { } exports.InternalNativeConnection = InternalNativeConnection; function isProtoMethod(method) { return 'resolvedResponseType' in method; } /** * See https://nodejs.org/api/timers.html#settimeoutcallback-delay-args * In particular, "When delay is larger than 2147483647 or less than 1, the * delay will be set to 1. Non-integer delays are truncated to an integer." * This number of milliseconds is almost 25 days. * * Copied from the grpc-js source code. */ const MAX_TIMEOUT_TIME = 2147483647; /** * Get the timeout value that should be passed to setTimeout now for the timer * to end at the deadline. For any deadline before now, the timer should end * immediately, represented by a value of 0. For any deadline more than * MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will * end at that time, so it is treated as infinitely far in the future. * * Copied from the grpc-js source code. */ function getRelativeTimeout(deadline) { const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline; const now = new Date().getTime(); const timeout = deadlineMs - now; if (timeout < 0) { return 0; } else if (timeout > MAX_TIMEOUT_TIME) { return Infinity; } else { return timeout; } } //# sourceMappingURL=connection.js.map