@camunda8/sdk
Version:
[](https://www.npmjs.com/package/@camunda8/sdk)
236 lines • 9.7 kB
JavaScript
;
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