@temporalio/workflow
Version:
Temporal.io SDK Workflow sub-package
201 lines • 8.62 kB
JavaScript
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
;