n8n
Version:
n8n Workflow Automation Tool
188 lines • 8.96 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DurableJobProvisioner = void 0;
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const scheduler_1 = require("@n8n/scheduler");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const scheduler_tracer_1 = require("./scheduler-tracer");
let DurableJobProvisioner = class DurableJobProvisioner {
constructor(logger, dataSource, jobs, tasks, globalConfig, tracing) {
this.logger = logger;
this.dataSource = dataSource;
this.jobs = jobs;
this.tasks = tasks;
this.globalConfig = globalConfig;
this.logger = this.logger.scoped('scheduler');
this.provisioner = (0, scheduler_1.createJobProvisioner)({
provisionTransaction: (scope) => this.provisionTransaction(scope),
deprovisionTransaction: (scope) => this.deprovisionTransaction(scope),
tracer: (0, scheduler_tracer_1.createSchedulerTracer)(tracing),
});
this.materializerOptions = {
...scheduler_1.DEFAULT_MATERIALIZER_OPTIONS,
windowSeconds: globalConfig.scheduler.materializationWindowSeconds,
defaultTimezone: globalConfig.generic.timezone,
};
}
async provision(workflowId, nodeId, taskType, payload, desired) {
return await this.provisioner.provision({ workflowId, nodeId, taskType, payload }, desired);
}
async deprovision(workflowId, nodeId) {
return await this.provisioner.deprovision({ workflowId, nodeId });
}
async deprovisionWorkflow(workflowId, taskType) {
return await this.provisioner.deprovision({ workflowId, taskType });
}
async deprovisionWorkflowInTransaction(manager, workflowId, taskType) {
await this.jobs.deleteByWorkflowTaskType(manager, workflowId, taskType);
}
provisionTransaction({ workflowId, nodeId, taskType, payload, }) {
return async (work) => await this.dataSource.transaction(async (manager) => {
const seededJobIds = new Set();
const result = await work({
findExisting: async () => {
const rows = await this.jobs.findManyByWorkflowNode(manager, workflowId, nodeId);
return rows.map((row) => ({
id: row.id,
name: row.name,
schedule: rowSchedule(row),
hasClock: row.nextRunAt !== null,
}));
},
insert: async (desired) => {
const rows = desired.map((job) => ({
name: job.name,
workflowId,
nodeId,
taskType,
payload,
...scheduleColumns(job.schedule),
nextRunAt: job.firstRunAt,
maxAttempts: this.globalConfig.scheduler.maxAttempts,
}));
const ids = await this.jobs.insertMany(manager, rows);
for (const id of ids)
seededJobIds.add(id);
return ids;
},
redefine: async (jobId, schedule, nextRunAt) => {
await this.jobs.updateDefinition(manager, jobId, {
...scheduleColumns(schedule),
nextRunAt,
});
seededJobIds.add(jobId);
},
withdrawPendingTasks: async (jobIds) => await this.tasks.deletePendingByJobIds(manager, jobIds),
deleteJobs: async (jobIds) => await this.jobs.deleteManyByIds(manager, jobIds),
});
await this.seedInitialOccurrences(manager, seededJobIds);
return result;
});
}
async seedInitialOccurrences(manager, jobIds) {
if (jobIds.size === 0)
return;
const now = await this.tasks.readDbTime();
const seedTransaction = async (work) => await work({
claimDueJobs: async () => {
const claimed = (await this.jobs.findManyByIds(manager, [...jobIds])).filter((job) => job.enabled && job.nextRunAt !== null);
return claimed.length > 0 ? { now, jobs: claimed } : undefined;
},
recordOccurrences: async (occurrences) => await this.tasks.insertIgnoringDuplicates(manager, occurrences),
advanceJobs: async (planned) => await this.jobs.advanceMany(manager, planned.map(({ job, plan }) => ({
id: job.id,
nextRunAt: plan.nextRunAt,
lastFiredAt: plan.lastFiredAt,
}))),
});
await (0, scheduler_1.materialize)(seedTransaction, this.materializerOptions, {
onPlanError: (job, error) => this.logger.error('Failed to plan a scheduled job while seeding its first run', {
jobId: job.id,
error: error instanceof Error ? error.message : String(error),
}),
onSkippedDuplicates: (context) => this.logger.debug('Seeding skipped occurrences already recorded for a scheduled job', {
...context,
}),
});
}
deprovisionTransaction(scope) {
return async (work) => await this.dataSource.transaction(async (manager) => await work({
deleteAll: async () => 'nodeId' in scope
? await this.jobs.deleteByWorkflowNode(manager, scope.workflowId, scope.nodeId)
: await this.jobs.deleteByWorkflowTaskType(manager, scope.workflowId, scope.taskType),
}));
}
};
exports.DurableJobProvisioner = DurableJobProvisioner;
exports.DurableJobProvisioner = DurableJobProvisioner = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger, db_1.DataSource, db_1.ScheduledJobRepository, db_1.ScheduledTaskRepository, config_1.GlobalConfig, n8n_core_1.Tracing])
], DurableJobProvisioner);
function scheduleColumns(schedule) {
const empty = {
cronExpression: null,
timezone: null,
recurrenceUnit: null,
recurrenceSize: null,
intervalSeconds: null,
fireAt: null,
};
switch (schedule.kind) {
case 'cron':
return {
...empty,
kind: schedule.kind,
cronExpression: schedule.cronExpression,
timezone: schedule.timezone,
};
case 'recurring_cron':
return {
...empty,
kind: schedule.kind,
cronExpression: schedule.cronExpression,
timezone: schedule.timezone,
recurrenceUnit: schedule.recurrenceUnit,
recurrenceSize: schedule.recurrenceSize,
};
case 'interval':
return { ...empty, kind: schedule.kind, intervalSeconds: schedule.intervalSeconds };
case 'one_off':
return { ...empty, kind: schedule.kind, fireAt: schedule.fireAt };
}
}
function rowSchedule(row) {
switch (row.kind) {
case 'cron':
return { kind: 'cron', cronExpression: row.cronExpression ?? '', timezone: row.timezone };
case 'recurring_cron':
return {
kind: 'recurring_cron',
cronExpression: row.cronExpression ?? '',
timezone: row.timezone,
recurrenceUnit: row.recurrenceUnit ?? 'hours',
recurrenceSize: row.recurrenceSize ?? 0,
};
case 'interval':
return { kind: 'interval', intervalSeconds: row.intervalSeconds ?? 0 };
case 'one_off':
return { kind: 'one_off', fireAt: row.fireAt ?? new Date(0) };
default: {
const exhaustive = row.kind;
throw new n8n_workflow_1.UnexpectedError(`Unexpected scheduled job kind: ${JSON.stringify(exhaustive)}`);
}
}
}
//# sourceMappingURL=durable-job-provisioner.js.map