UNPKG

@temporalio/workflow

Version:
201 lines 8.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.initRuntime = initRuntime; exports.initialize = initialize; exports.activate = activate; exports.concludeActivation = concludeActivation; exports.tryUnblockConditions = tryUnblockConditions; exports.dispose = dispose; /** * Exported functions for the Worker to interact with the Workflow isolate * * @module */ const common_1 = require("@temporalio/common"); const interceptors_1 = require("@temporalio/common/lib/interceptors"); const cancellation_scope_1 = require("./cancellation-scope"); const update_scope_1 = require("./update-scope"); const internals_1 = require("./internals"); const global_attributes_1 = require("./global-attributes"); const global = globalThis; const OriginalDate = globalThis.Date; /** * Initialize the isolate runtime. * * Sets required internal state and instantiates the workflow and interceptors. */ function initRuntime(options) { const activator = new internals_1.Activator({ ...options, info: fixPrototypes({ ...options.info, unsafe: { ...options.info.unsafe, now: OriginalDate.now }, }), }); // There's one activator per workflow instance, set it globally on the context. // We do this before importing any user code so user code can statically reference @temporalio/workflow functions // as well as Date and Math.random. (0, global_attributes_1.setActivatorUntyped)(activator); // webpack alias to payloadConverterPath // eslint-disable-next-line @typescript-eslint/no-require-imports const customPayloadConverter = require('__temporal_custom_payload_converter').payloadConverter; // The `payloadConverter` export is validated in the Worker if (customPayloadConverter != null) { activator.payloadConverter = customPayloadConverter; } // webpack alias to failureConverterPath // eslint-disable-next-line @typescript-eslint/no-require-imports const customFailureConverter = require('__temporal_custom_failure_converter').failureConverter; // The `failureConverter` export is validated in the Worker if (customFailureConverter != null) { activator.failureConverter = customFailureConverter; } const { importWorkflows, importInterceptors } = global.__TEMPORAL__; if (importWorkflows === undefined || importInterceptors === undefined) { throw new common_1.IllegalStateError('Workflow bundle did not register import hooks'); } const interceptors = importInterceptors(); for (const mod of interceptors) { const factory = mod.interceptors; if (factory !== undefined) { if (typeof factory !== 'function') { throw new TypeError(`Failed to initialize workflows interceptors: expected a function, but got: '${factory}'`); } const interceptors = factory(); activator.interceptors.inbound.push(...(interceptors.inbound ?? [])); activator.interceptors.outbound.push(...(interceptors.outbound ?? [])); activator.interceptors.internals.push(...(interceptors.internals ?? [])); } } const mod = importWorkflows(); const workflowFn = mod[activator.info.workflowType]; const defaultWorkflowFn = mod['default']; if (typeof workflowFn === 'function') { activator.workflow = workflowFn; } else if (typeof defaultWorkflowFn === 'function') { activator.workflow = defaultWorkflowFn; } else { const details = workflowFn === undefined ? 'no such function is exported by the workflow bundle' : `expected a function, but got: '${typeof workflowFn}'`; throw new TypeError(`Failed to initialize workflow of type '${activator.info.workflowType}': ${details}`); } } /** * Objects transfered to the VM from outside have prototypes belonging to the * outer context, which means that instanceof won't work inside the VM. This * function recursively walks over the content of an object, and recreate some * of these objects (notably Array, Date and Objects). */ function fixPrototypes(obj) { if (obj != null && typeof obj === 'object') { switch (Object.getPrototypeOf(obj)?.constructor?.name) { case 'Array': return Array.from(obj.map(fixPrototypes)); case 'Date': return new Date(obj); default: return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fixPrototypes(v)])); } } else return obj; } /** * Initialize the workflow. Or to be exact, _complete_ initialization, as most part has been done in constructor). */ function initialize(initializeWorkflowJob) { (0, global_attributes_1.getActivator)().initializeWorkflow(initializeWorkflowJob); } /** * Run a chunk of activation jobs */ function activate(activation, batchIndex = 0) { const activator = (0, global_attributes_1.getActivator)(); const intercept = (0, interceptors_1.composeInterceptors)(activator.interceptors.internals, 'activate', ({ activation }) => { // Cast from the interface to the class which has the `variant` attribute. // This is safe because we know that activation is a proto class. const jobs = activation.jobs; // Initialization will have been handled already, but we might still need to start the workflow function const startWorkflowJob = jobs[0].variant === 'initializeWorkflow' ? jobs.shift()?.initializeWorkflow : undefined; for (const job of jobs) { if (job.variant === undefined) throw new TypeError('Expected job.variant to be defined'); const variant = job[job.variant]; if (!variant) throw new TypeError(`Expected job.${job.variant} to be set`); activator[job.variant](variant /* TS can't infer this type */); if (job.variant !== 'queryWorkflow') tryUnblockConditions(); } if (startWorkflowJob) { const safeJobTypes = [ 'initializeWorkflow', 'signalWorkflow', 'doUpdate', 'cancelWorkflow', 'updateRandomSeed', ]; if (jobs.some((job) => !safeJobTypes.includes(job.variant))) { throw new TypeError('Received both initializeWorkflow and non-signal/non-update jobs in the same activation: ' + JSON.stringify(jobs.map((job) => job.variant))); } activator.startWorkflow(startWorkflowJob); tryUnblockConditions(); } }); intercept({ activation, batchIndex }); } /** * Conclude a single activation. * Should be called after processing all activation jobs and queued microtasks. * * Activation failures are handled in the main Node.js isolate. */ function concludeActivation() { const activator = (0, global_attributes_1.getActivator)(); activator.rejectBufferedUpdates(); const intercept = (0, interceptors_1.composeInterceptors)(activator.interceptors.internals, 'concludeActivation', (input) => input); const activationCompletion = activator.concludeActivation(); const { commands } = intercept({ commands: activationCompletion.commands }); if (activator.completed) { activator.warnIfUnfinishedHandlers(); } return { runId: activator.info.runId, successful: { ...activationCompletion, commands }, }; } /** * Loop through all blocked conditions, evaluate and unblock if possible. * * @returns number of unblocked conditions. */ function tryUnblockConditions() { let numUnblocked = 0; for (;;) { const prevUnblocked = numUnblocked; for (const [seq, cond] of (0, global_attributes_1.getActivator)().blockedConditions.entries()) { if (cond.fn()) { cond.resolve(); numUnblocked++; // It is safe to delete elements during map iteration (0, global_attributes_1.getActivator)().blockedConditions.delete(seq); } } if (prevUnblocked === numUnblocked) { break; } } return numUnblocked; } function dispose() { const dispose = (0, interceptors_1.composeInterceptors)((0, global_attributes_1.getActivator)().interceptors.internals, 'dispose', async () => { (0, cancellation_scope_1.disableStorage)(); (0, update_scope_1.disableUpdateStorage)(); }); dispose({}); } //# sourceMappingURL=worker-interface.js.map