backend v4 half

This commit is contained in:
Donat committed 2025-07-18 09:20:40 +02:00
1 parent aba7a506ad
commit 725516ad6c
4183 files changed
+217684 -75056

No files matched your search

@@ -0,0 +1,42 @@
import { Execution } from "../tasks/scheduled-task";
import { TimeMatcher } from "../time/time-matcher";
type OnFn = (date: Date) => void | Promise<void>;
type OnErrorHookFn = (date: Date, error: Error, execution: Execution) => void | Promise<void>;
type OnHookFn = (date: Date, execution: Execution) => boolean | Promise<boolean>;
type OnMatch = (date: Date, execution: Execution) => any | Promise<any>;
export type RunnerOptions = {
noOverlap?: boolean;
timezone?: string;
maxExecutions?: number;
maxRandomDelay?: number;
onMissedExecution?: OnFn;
onOverlap?: OnFn;
onError?: OnErrorHookFn;
onFinished?: OnHookFn;
beforeRun?: OnHookFn;
onMaxExecutions?: OnFn;
};
export declare class Runner {
timeMatcher: TimeMatcher;
onMatch: OnMatch;
noOverlap: boolean;
maxExecutions?: number;
maxRandomDelay: number;
runCount: number;
running: boolean;
heartBeatTimeout?: NodeJS.Timeout;
onMissedExecution: OnFn;
onOverlap: OnFn;
onError: OnErrorHookFn;
beforeRun: OnHookFn;
onFinished: OnHookFn;
onMaxExecutions: OnFn;
constructor(timeMatcher: TimeMatcher, onMatch: OnMatch, options?: RunnerOptions);
start(): void;
nextRun(): Date;
stop(): void;
isStarted(): boolean;
isStopped(): boolean;
execute(): Promise<void>;
}
export {};
+192
View File
@@ -0,0 +1,192 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runner = void 0;
const create_id_1 = require("../create-id");
const logger_1 = __importDefault(require("../logger"));
const tracked_promise_1 = require("../promise/tracked-promise");
function emptyOnFn() { }
;
function emptyHookFn() { return true; }
;
function defaultOnError(date, error) {
logger_1.default.error('Task failed with error!', error);
}
class Runner {
timeMatcher;
onMatch;
noOverlap;
maxExecutions;
maxRandomDelay;
runCount;
running;
heartBeatTimeout;
onMissedExecution;
onOverlap;
onError;
beforeRun;
onFinished;
onMaxExecutions;
constructor(timeMatcher, onMatch, options) {
this.timeMatcher = timeMatcher;
this.onMatch = onMatch;
this.noOverlap = options == undefined || options.noOverlap === undefined ? false : options.noOverlap;
this.maxExecutions = options?.maxExecutions;
this.maxRandomDelay = options?.maxRandomDelay || 0;
this.onMissedExecution = options?.onMissedExecution || emptyOnFn;
this.onOverlap = options?.onOverlap || emptyOnFn;
this.onError = options?.onError || defaultOnError;
this.onFinished = options?.onFinished || emptyHookFn;
this.beforeRun = options?.beforeRun || emptyHookFn;
this.onMaxExecutions = options?.onMaxExecutions || emptyOnFn;
this.runCount = 0;
this.running = false;
}
start() {
this.running = true;
let lastExecution;
let expectedNextExecution;
const scheduleNextHeartBeat = (currentDate) => {
if (this.running) {
clearTimeout(this.heartBeatTimeout);
this.heartBeatTimeout = setTimeout(heartBeat, getDelay(this.timeMatcher, currentDate));
}
};
const runTask = (date) => {
return new Promise(async (resolve) => {
const execution = {
id: (0, create_id_1.createID)('exec'),
reason: 'scheduled'
};
const shouldExecute = await this.beforeRun(date, execution);
const randomDelay = Math.floor(Math.random() * this.maxRandomDelay);
if (shouldExecute) {
setTimeout(async () => {
try {
this.runCount++;
execution.startedAt = new Date();
const result = await this.onMatch(date, execution);
execution.finishedAt = new Date();
execution.result = result;
this.onFinished(date, execution);
if (this.maxExecutions && this.runCount >= this.maxExecutions) {
this.onMaxExecutions(date);
this.stop();
}
}
catch (error) {
execution.finishedAt = new Date();
execution.error = error;
this.onError(date, error, execution);
}
resolve(true);
}, randomDelay);
}
});
};
const checkAndRun = (date) => {
return new tracked_promise_1.TrackedPromise(async (resolve, reject) => {
try {
if (this.timeMatcher.match(date)) {
await runTask(date);
}
resolve(true);
}
catch (err) {
reject(err);
}
});
};
const heartBeat = async () => {
const currentDate = nowWithoutMs();
if (expectedNextExecution && expectedNextExecution.getTime() < currentDate.getTime()) {
while (expectedNextExecution.getTime() < currentDate.getTime()) {
logger_1.default.warn(`missed execution at ${expectedNextExecution}! Possible blocking IO or high CPU user at the same process used by node-cron.`);
expectedNextExecution = this.timeMatcher.getNextMatch(expectedNextExecution);
runAsync(this.onMissedExecution, expectedNextExecution, defaultOnError);
}
}
if (lastExecution && lastExecution.getState() === 'pending') {
runAsync(this.onOverlap, currentDate, defaultOnError);
if (this.noOverlap) {
logger_1.default.warn('task still running, new execution blocked by overlap prevention!');
expectedNextExecution = this.timeMatcher.getNextMatch(currentDate);
scheduleNextHeartBeat(currentDate);
return;
}
}
lastExecution = checkAndRun(currentDate);
expectedNextExecution = this.timeMatcher.getNextMatch(currentDate);
scheduleNextHeartBeat(currentDate);
};
this.heartBeatTimeout = setTimeout(() => {
heartBeat();
}, getDelay(this.timeMatcher, nowWithoutMs()));
}
nextRun() {
return this.timeMatcher.getNextMatch(new Date());
}
stop() {
this.running = false;
if (this.heartBeatTimeout) {
clearTimeout(this.heartBeatTimeout);
this.heartBeatTimeout = undefined;
}
}
isStarted() {
return !!this.heartBeatTimeout && this.running;
}
isStopped() {
return !this.isStarted();
}
async execute() {
const date = new Date();
const execution = {
id: (0, create_id_1.createID)('exec'),
reason: 'invoked'
};
try {
const shouldExecute = await this.beforeRun(date, execution);
if (shouldExecute) {
this.runCount++;
execution.startedAt = new Date();
const result = await this.onMatch(date, execution);
execution.finishedAt = new Date();
execution.result = result;
this.onFinished(date, execution);
}
}
catch (error) {
execution.finishedAt = new Date();
execution.error = error;
this.onError(date, error, execution);
}
}
}
exports.Runner = Runner;
async function runAsync(fn, date, onError) {
try {
await fn(date);
}
catch (error) {
onError(date, error);
}
}
function getDelay(timeMatcher, currentDate) {
const maxDelay = 86400000;
const nextRun = timeMatcher.getNextMatch(currentDate);
const now = new Date();
const delay = nextRun.getTime() - now.getTime();
if (delay > maxDelay) {
return maxDelay;
}
return Math.max(0, delay);
}
function nowWithoutMs() {
const date = new Date();
date.setMilliseconds(0);
return date;
}
//# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/scheduler/runner.ts"],"names":[],"mappings":"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 truncated