UNPKG

@camunda8/sdk

Version:

[![NPM](https://nodei.co/npm/@camunda8/sdk.png)](https://www.npmjs.com/package/@camunda8/sdk)

236 lines 9.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ZBStreamWorker = void 0; const chalk_1 = __importDefault(require("chalk")); const interfaces_1_0_1 = require("./interfaces-1.0"); const _1 = require("."); class ZBStreamWorker { constructor({ grpcClient, log, zbClient, }) { this.streams = []; this.pollTimers = []; this.grpcClient = grpcClient; this.logger = log; this.zbClient = zbClient; if (!this.zbClient) { console.log(`Missing ZBClient`); } } streamJobs(req) { const { taskHandler, inputVariableDto, customHeadersDto, jitter, pollMaxJobsToActivate = 32, pollInterval = 30000, ...streamReq } = req; const handleJob = (job) => { taskHandler({ ...job, // eslint-disable-next-line @typescript-eslint/no-explicit-any ...this.makeCompleteHandlers(job, req.type), }, this); }; const pollAndStream = async () => { // Optional jitter to stagger multiple workers if (jitter) { await new Promise((resolve) => setTimeout(resolve, Math.random() * jitter)); } // Open the stream first so no new jobs are missed const stream = await this.grpcClient.streamActivatedJobsStream(streamReq); stream.on('error', (e) => { console.error(e); }); stream.on('data', (res) => { try { const parsedJob = (0, _1.parseVariablesAndCustomHeadersToJSON)(res, inputVariableDto, customHeadersDto); handleJob(parsedJob); } catch (e) { this.zbClient.failJob({ jobKey: res.key, errorMessage: `Error parsing variable payload ${e}`, retries: res.retries - 1, retryBackOff: 0, }); } }); this.streams.push(stream); // Helper to run a single poll cycle and feed results to handleJob. const runPoll = async () => { const jobs = await this.zbClient.activateJobs({ type: req.type, worker: req.worker, timeout: req.timeout, maxJobsToActivate: pollMaxJobsToActivate, tenantIds: req.tenantIds, fetchVariable: req.fetchVariable, inputVariableDto, customHeadersDto, requestTimeout: -1, }); for (const job of jobs) { handleJob(job); } }; // Initial backfill poll: pick up jobs that existed before the stream. // The broker guarantees single activation, so there is no risk of // duplicate delivery between the poll and the stream. await runPoll(); // Recurring sidecar poll: a low-frequency safety net that catches // jobs the stream may have missed (e.g. jobs re-queued after a // timeout or during a brief stream reconnect). Uses a setTimeout // chain so each cycle waits for the previous poll to finish before // scheduling the next, preventing overlapping polls. let sidecarTimer; if (pollInterval > 0) { const schedulePoll = () => { sidecarTimer = setTimeout(() => { runPoll() .catch(() => { // Swallow errors — the stream is the primary channel. // The next poll cycle will retry. }) .finally(schedulePoll); }, pollInterval); this.pollTimers.push(sidecarTimer); }; schedulePoll(); } return { close: () => { if (sidecarTimer) { clearTimeout(sidecarTimer); } stream.cancel(); stream.destroy(); }, }; }; return pollAndStream(); } close() { this.pollTimers.forEach((t) => clearTimeout(t)); this.pollTimers = []; this.streams.forEach((s) => { s.cancel(); s.destroy(); }); return this.grpcClient.close(); } log(msg) { this.logger.logInfo(msg); } debug(msg) { this.logger.logDebug(msg); } error(msg) { this.logger.logError(msg); } makeCompleteHandlers(thisJob, taskType) { let methodCalled; /** * This is a wrapper that allows us to throw an error if a job acknowledgement function is called more than once, * for these functions should be called once only (and only one should be called, but we don't handle that case). */ const errorMsgOnPriorMessageCall = (thisMethod, wrappedFunction) => { return (...args) => { if (methodCalled !== undefined) { // tslint:disable-next-line: no-console console.log(chalk_1.default.red(`WARNING: Call to ${thisMethod}() after ${methodCalled}() was called. You should call only one job action method in the worker handler. This is a bug in a Stream worker handler.`)); return wrappedFunction(...args); } methodCalled = thisMethod; return wrappedFunction(...args); }; }; const cancelWorkflow = (job) => () => this.zbClient .cancelProcessInstance(job.processInstanceKey) .then(() => interfaces_1_0_1.JOB_ACTION_ACKNOWLEDGEMENT); const failJob = (job) => (conf, retries) => { const isFailureConfig = (_conf) => typeof _conf === 'object'; const errorMessage = isFailureConfig(conf) ? conf.errorMessage : conf; const retryBackOff = isFailureConfig(conf) ? (conf.retryBackOff ?? 0) : 0; const _retries = isFailureConfig(conf) ? (conf.retries ?? 0) : retries; return this.failJob({ job, errorMessage, retries: _retries, retryBackOff, }); }; const succeedJob = (job) => (completedVariables) => this.completeJob(job.key, completedVariables ?? {}, taskType); const errorJob = (job) => (e, errorMessage = '') => { const isErrorJobWithVariables = (s) => typeof s === 'object'; const errorCode = isErrorJobWithVariables(e) ? e.errorCode : e; errorMessage = isErrorJobWithVariables(e) ? (e.errorMessage ?? '') : errorMessage; const variables = isErrorJobWithVariables(e) ? e.variables : {}; return this.errorJob({ errorCode, errorMessage, job, variables, }); }; const fail = failJob(thisJob); const succeed = succeedJob(thisJob); return { cancelWorkflow: cancelWorkflow(thisJob), complete: errorMsgOnPriorMessageCall('job.complete', succeed), error: errorMsgOnPriorMessageCall('error', errorJob(thisJob)), fail: errorMsgOnPriorMessageCall('job.fail', fail), forward: errorMsgOnPriorMessageCall('job.forward', () => { return interfaces_1_0_1.JOB_ACTION_ACKNOWLEDGEMENT; }), }; } failJob({ job, errorMessage, retries, retryBackOff, }) { return this.zbClient .failJob({ errorMessage, jobKey: job.key, retries: retries ?? job.retries - 1, retryBackOff: retryBackOff ?? 0, }) .then(() => interfaces_1_0_1.JOB_ACTION_ACKNOWLEDGEMENT) .finally(() => { this.logger.logDebug(`Failed job ${job.key} - ${errorMessage}`); }); } completeJob(jobKey, completedVariables = {}, taskType) { return this.zbClient .completeJob({ jobKey, variables: completedVariables, }) .then((res) => { this.logger.logDebug(`Completed job ${jobKey} for ${taskType}`); return res; }) .catch((e) => { this.logger.logDebug(`Completing job ${jobKey} for ${taskType} threw ${e.message}`); return e; }) .then(() => interfaces_1_0_1.JOB_ACTION_ACKNOWLEDGEMENT); } errorJob({ errorCode, errorMessage, job, variables, }) { return this.zbClient .throwError({ errorCode, errorMessage, jobKey: job.key, variables, }) .then(() => this.logger.logDebug(`Errored job ${job.key} - ${errorMessage}`)) .catch((e) => { this.logger.logError(`Exception while attempting to raise BPMN Error for job ${job.key} - ${errorMessage}`); this.logger.logError(e); }) .then(() => { return interfaces_1_0_1.JOB_ACTION_ACKNOWLEDGEMENT; }); } } exports.ZBStreamWorker = ZBStreamWorker; //# sourceMappingURL=ZBStreamWorker.js.map