Inital Commit

This commit is contained in:
root
2025-06-24 15:43:32 +02:00
commit 23341b8abb
4657 changed files with 549083 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import { ChildProcess } from 'child_process';
import { ScheduledTask, TaskContext, TaskEvent, TaskOptions } from '../scheduled-task';
import { EventEmitter } from 'stream';
import { StateMachine } from '../state-machine';
declare class TaskEmitter extends EventEmitter {
}
declare class BackgroundScheduledTask implements ScheduledTask {
emitter: TaskEmitter;
id: string;
name: string;
cronExpression: any;
taskPath: any;
options?: any;
forkProcess?: ChildProcess;
stateMachine: StateMachine;
constructor(cronExpression: string, taskPath: string, options?: TaskOptions);
getNextRun(): Date | null;
start(): Promise<void>;
stop(): Promise<void>;
getStatus(): string;
destroy(): Promise<void>;
execute(): Promise<any>;
on(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
off(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
once(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
private createContext;
}
export default BackgroundScheduledTask;

View File

@@ -0,0 +1,220 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const child_process_1 = require("child_process");
const create_id_1 = require("../../create-id");
const stream_1 = require("stream");
const state_machine_1 = require("../state-machine");
const localized_time_1 = require("../../time/localized-time");
const logger_1 = __importDefault(require("../../logger"));
const time_matcher_1 = require("../../time/time-matcher");
const daemonPath = (0, path_1.resolve)(__dirname, 'daemon.js');
class TaskEmitter extends stream_1.EventEmitter {
}
class BackgroundScheduledTask {
emitter;
id;
name;
cronExpression;
taskPath;
options;
forkProcess;
stateMachine;
constructor(cronExpression, taskPath, options) {
this.cronExpression = cronExpression;
this.taskPath = taskPath;
this.options = options;
this.id = (0, create_id_1.createID)('task');
this.name = options?.name || this.id;
this.emitter = new TaskEmitter();
this.stateMachine = new state_machine_1.StateMachine('stopped');
this.on('task:stopped', () => {
this.forkProcess?.kill();
this.forkProcess = undefined;
this.stateMachine.changeState('stopped');
});
this.on('task:destroyed', () => {
this.forkProcess?.kill();
this.forkProcess = undefined;
this.stateMachine.changeState('destroyed');
});
}
getNextRun() {
if (this.stateMachine.state !== 'stopped') {
const timeMatcher = new time_matcher_1.TimeMatcher(this.cronExpression, this.options?.timezone);
return timeMatcher.getNextMatch(new Date());
}
return null;
}
start() {
return new Promise((resolve, reject) => {
if (this.forkProcess) {
return resolve(undefined);
}
const timeout = setTimeout(() => {
reject(new Error('Start operation timed out'));
}, 5000);
try {
this.forkProcess = (0, child_process_1.fork)(daemonPath);
this.forkProcess.on('error', (err) => {
clearTimeout(timeout);
reject(new Error(`Error on daemon: ${err.message}`));
});
this.forkProcess.on('exit', (code, signal) => {
if (code !== 0 && signal !== 'SIGTERM') {
const erro = new Error(`node-cron daemon exited with code ${code || signal}`);
logger_1.default.error(erro);
clearTimeout(timeout);
reject(erro);
}
});
this.forkProcess.on('message', (message) => {
if (message.jsonError) {
if (message.context?.execution) {
message.context.execution.error = deserializeError(message.jsonError);
delete message.jsonError;
}
}
if (message.context?.task?.state) {
this.stateMachine.changeState(message.context?.task?.state);
}
if (message.context) {
const execution = message.context?.execution;
delete execution?.hasError;
const context = this.createContext(new Date(message.context.date), execution);
this.emitter.emit(message.event, context);
}
});
this.once('task:started', () => {
this.stateMachine.changeState('idle');
clearTimeout(timeout);
resolve(undefined);
});
this.forkProcess.send({
command: 'task:start',
path: (0, path_1.resolve)(this.taskPath),
cron: this.cronExpression,
options: this.options
});
}
catch (error) {
reject(error);
}
});
}
stop() {
return new Promise((resolve, reject) => {
if (!this.forkProcess) {
return resolve(undefined);
}
const timeoutId = setTimeout(() => {
clearTimeout(timeoutId);
reject(new Error('Stop operation timed out'));
}, 5000);
const cleanupAndResolve = () => {
clearTimeout(timeoutId);
this.off('task:stopped', onStopped);
this.forkProcess = undefined;
resolve(undefined);
};
const onStopped = () => {
cleanupAndResolve();
};
this.once('task:stopped', onStopped);
this.forkProcess.send({
command: 'task:stop'
});
});
}
getStatus() {
return this.stateMachine.state;
}
destroy() {
return new Promise((resolve, reject) => {
if (!this.forkProcess) {
return resolve(undefined);
}
const timeoutId = setTimeout(() => {
clearTimeout(timeoutId);
reject(new Error('Destroy operation timed out'));
}, 5000);
const onDestroy = () => {
clearTimeout(timeoutId);
this.off('task:destroyed', onDestroy);
resolve(undefined);
};
this.once('task:destroyed', onDestroy);
this.forkProcess.send({
command: 'task:destroy'
});
});
}
execute() {
return new Promise((resolve, reject) => {
if (!this.forkProcess) {
return reject(new Error('Cannot execute background task because it hasn\'t been started yet. Please initialize the task using the start() method before attempting to execute it.'));
}
const timeoutId = setTimeout(() => {
cleanupListeners();
reject(new Error('Execution timeout exceeded'));
}, 5000);
const cleanupListeners = () => {
clearTimeout(timeoutId);
this.off('execution:finished', onFinished);
this.off('execution:failed', onFail);
};
const onFinished = (context) => {
cleanupListeners();
resolve(context.execution?.result);
};
const onFail = (context) => {
cleanupListeners();
reject(context.execution?.error || new Error('Execution failed without specific error'));
};
this.once('execution:finished', onFinished);
this.once('execution:failed', onFail);
this.forkProcess.send({
command: 'task:execute'
});
});
}
on(event, fun) {
this.emitter.on(event, fun);
}
off(event, fun) {
this.emitter.off(event, fun);
}
once(event, fun) {
this.emitter.once(event, fun);
}
createContext(executionDate, execution) {
const localTime = new localized_time_1.LocalizedTime(executionDate, this.options?.timezone);
const ctx = {
date: localTime.toDate(),
dateLocalIso: localTime.toISO(),
triggeredAt: new Date(),
task: this,
execution: execution
};
return ctx;
}
}
function deserializeError(str) {
const data = JSON.parse(str);
const Err = globalThis[data.name] || Error;
const err = new Err(data.message);
if (data.stack) {
err.stack = data.stack;
}
Object.keys(data).forEach(key => {
if (!['name', 'message', 'stack'].includes(key)) {
err[key] = data[key];
}
});
return err;
}
exports.default = BackgroundScheduledTask;
//# sourceMappingURL=background-scheduled-task.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import { ScheduledTask } from "../scheduled-task";
export declare function startDaemon(message: any): Promise<ScheduledTask>;
export declare function bind(): void;

View File

@@ -0,0 +1,135 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startDaemon = startDaemon;
exports.bind = bind;
const logger_1 = __importDefault(require("../../logger"));
const inline_scheduled_task_1 = require("../inline-scheduled-task");
async function startDaemon(message) {
const script = await Promise.resolve(`${message.path}`).then(s => __importStar(require(s)));
const task = new inline_scheduled_task_1.InlineScheduledTask(message.cron, script.task, message.options);
task.on('task:started', (context => sendEvent('task:started', context)));
task.on('task:stopped', (context => sendEvent('task:stopped', context)));
task.on('task:destroyed', (context => sendEvent('task:destroyed', context)));
task.on('execution:started', (context => sendEvent('execution:started', context)));
task.on('execution:finished', (context => sendEvent('execution:finished', context)));
task.on('execution:failed', (context => sendEvent('execution:failed', context)));
task.on('execution:missed', (context => sendEvent('execution:missed', context)));
task.on('execution:overlap', (context => sendEvent('execution:overlap', context)));
task.on('execution:maxReached', (context => sendEvent('execution:maxReached', context)));
if (process.send)
process.send({ event: 'daemon:started' });
task.start();
return task;
}
function sendEvent(event, context) {
const message = { event: event, context: safelySerializeContext(context) };
if (context.execution?.error) {
message.jsonError = serializeError(context.execution?.error);
}
if (process.send)
process.send(message);
}
function serializeError(err) {
const plain = {
name: err.name,
message: err.message,
stack: err.stack,
...Object.getOwnPropertyNames(err)
.filter(k => !['name', 'message', 'stack'].includes(k))
.reduce((acc, k) => {
acc[k] = err[k];
return acc;
}, {})
};
return JSON.stringify(plain);
}
function safelySerializeContext(context) {
const safeContext = {
date: context.date,
dateLocalIso: context.dateLocalIso,
triggeredAt: context.triggeredAt
};
if (context.task) {
safeContext.task = {
id: context.task.id,
name: context.task.name,
status: context.task.getStatus()
};
}
if (context.execution) {
safeContext.execution = {
id: context.execution.id,
reason: context.execution.reason,
startedAt: context.execution.startedAt,
finishedAt: context.execution.finishedAt,
hasError: !!context.execution.error,
result: context.execution.result
};
}
return safeContext;
}
function bind() {
let task;
process.on('message', async (message) => {
switch (message.command) {
case 'task:start':
task = await startDaemon(message);
return task;
case 'task:stop':
if (task)
task.stop();
return task;
case 'task:destroy':
if (task)
task.destroy();
return task;
case 'task:execute':
try {
if (task)
await task.execute();
}
catch (error) {
logger_1.default.debug('Daemon task:execute falied:', error);
}
return task;
}
});
}
bind();
//# sourceMappingURL=daemon.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../../src/tasks/background-scheduled-task/daemon.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/tasks/background-scheduled-task/index.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,30 @@
import EventEmitter from "events";
import { ScheduledTask, TaskContext, TaskEvent, TaskFn, TaskOptions } from "./scheduled-task";
import { Runner } from "../scheduler/runner";
import { TimeMatcher } from "../time/time-matcher";
import { StateMachine } from "./state-machine";
declare class TaskEmitter extends EventEmitter {
}
export declare class InlineScheduledTask implements ScheduledTask {
emitter: TaskEmitter;
cronExpression: string;
timeMatcher: TimeMatcher;
runner: Runner;
id: string;
name: string;
stateMachine: StateMachine;
timezone?: string;
constructor(cronExpression: string, taskFn: TaskFn, options?: TaskOptions);
getNextRun(): Date | null;
private changeState;
start(): void;
stop(): void;
getStatus(): string;
destroy(): void;
execute(): Promise<any>;
on(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
off(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
once(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
private createContext;
}
export {};

View File

@@ -0,0 +1,143 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlineScheduledTask = void 0;
const events_1 = __importDefault(require("events"));
const runner_1 = require("../scheduler/runner");
const time_matcher_1 = require("../time/time-matcher");
const create_id_1 = require("../create-id");
const state_machine_1 = require("./state-machine");
const logger_1 = __importDefault(require("../logger"));
const localized_time_1 = require("../time/localized-time");
class TaskEmitter extends events_1.default {
}
class InlineScheduledTask {
emitter;
cronExpression;
timeMatcher;
runner;
id;
name;
stateMachine;
timezone;
constructor(cronExpression, taskFn, options) {
this.emitter = new TaskEmitter();
this.cronExpression = cronExpression;
this.id = (0, create_id_1.createID)('task', 12);
this.name = options?.name || this.id;
this.timezone = options?.timezone;
this.timeMatcher = new time_matcher_1.TimeMatcher(cronExpression, options?.timezone);
this.stateMachine = new state_machine_1.StateMachine();
const runnerOptions = {
timezone: options?.timezone,
noOverlap: options?.noOverlap,
maxExecutions: options?.maxExecutions,
beforeRun: (date, execution) => {
if (execution.reason === 'scheduled') {
this.changeState('running');
}
this.emitter.emit('execution:started', this.createContext(date, execution));
return true;
},
onFinished: (date, execution) => {
if (execution.reason === 'scheduled') {
this.changeState('idle');
}
this.emitter.emit('execution:finished', this.createContext(date, execution));
return true;
},
onError: (date, error, execution) => {
logger_1.default.error(error);
this.emitter.emit('execution:failed', this.createContext(date, execution));
this.changeState('idle');
},
onOverlap: (date) => {
this.emitter.emit('execution:overlap', this.createContext(date));
},
onMissedExecution: (date) => {
this.emitter.emit('execution:missed', this.createContext(date));
},
onMaxExecutions: (date) => {
this.emitter.emit('execution:maxReached', this.createContext(date));
this.destroy();
}
};
this.runner = new runner_1.Runner(this.timeMatcher, (date, execution) => {
return taskFn(this.createContext(date, execution));
}, runnerOptions);
}
getNextRun() {
if (this.stateMachine.state !== 'stopped') {
return this.runner.nextRun();
}
return null;
}
changeState(state) {
if (this.runner.isStarted()) {
this.stateMachine.changeState(state);
}
}
start() {
if (this.runner.isStopped()) {
this.runner.start();
this.stateMachine.changeState('idle');
this.emitter.emit('task:started', this.createContext(new Date()));
}
}
stop() {
if (this.runner.isStarted()) {
this.runner.stop();
this.stateMachine.changeState('stopped');
this.emitter.emit('task:stopped', this.createContext(new Date()));
}
}
getStatus() {
return this.stateMachine.state;
}
destroy() {
if (this.stateMachine.state === 'destroyed')
return;
this.stop();
this.stateMachine.changeState('destroyed');
this.emitter.emit('task:destroyed', this.createContext(new Date()));
}
execute() {
return new Promise((resolve, reject) => {
const onFail = (context) => {
this.off('execution:finished', onFail);
reject(context.execution?.error);
};
const onFinished = (context) => {
this.off('execution:failed', onFail);
resolve(context.execution?.result);
};
this.once('execution:finished', onFinished);
this.once('execution:failed', onFail);
this.runner.execute();
});
}
on(event, fun) {
this.emitter.on(event, fun);
}
off(event, fun) {
this.emitter.off(event, fun);
}
once(event, fun) {
this.emitter.once(event, fun);
}
createContext(executionDate, execution) {
const localTime = new localized_time_1.LocalizedTime(executionDate, this.timezone);
const ctx = {
date: localTime.toDate(),
dateLocalIso: localTime.toISO(),
triggeredAt: new Date(),
task: this,
execution: execution
};
return ctx;
}
}
exports.InlineScheduledTask = InlineScheduledTask;
//# sourceMappingURL=inline-scheduled-task.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"inline-scheduled-task.js","sourceRoot":"","sources":["../../../src/tasks/inline-scheduled-task.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,36 @@
export type TaskContext = {
date: Date;
dateLocalIso: string;
task?: ScheduledTask;
execution?: Execution;
triggeredAt: Date;
};
export type TaskEvent = 'task:started' | 'task:stopped' | 'task:destroyed' | 'execution:started' | 'execution:finished' | 'execution:failed' | 'execution:missed' | 'execution:overlap' | 'execution:maxReached';
export type TaskOptions = {
timezone?: string;
name?: string;
noOverlap?: boolean;
maxExecutions?: number;
};
export type Execution = {
id: string;
reason: 'invoked' | 'scheduled';
startedAt?: Date;
finishedAt?: Date;
error?: Error;
result?: any;
};
export type TaskFn = (context: TaskContext) => any | Promise<any>;
export interface ScheduledTask {
id: string;
name?: string;
start(): void | Promise<void>;
stop(): void | Promise<void>;
getStatus(): string | Promise<string>;
destroy(): void | Promise<void>;
execute(): Promise<any>;
getNextRun(): Date | null;
on(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
off(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
once(event: TaskEvent, fun: (context: TaskContext) => Promise<void> | void): void;
}

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=scheduled-task.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"scheduled-task.js","sourceRoot":"","sources":["../../../src/tasks/scheduled-task.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,6 @@
export type TaskState = 'stopped' | 'idle' | 'running' | 'destroyed';
export declare class StateMachine {
state: TaskState;
constructor(initial?: TaskState);
changeState(state: TaskState): void;
}

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StateMachine = void 0;
const allowedTransitions = {
'stopped': ['stopped', 'idle', 'destroyed'],
'idle': ['idle', 'running', 'stopped', 'destroyed'],
'running': ['running', 'idle', 'stopped', 'destroyed'],
'destroyed': ['destroyed']
};
class StateMachine {
state;
constructor(initial = 'stopped') {
this.state = initial;
}
changeState(state) {
if (allowedTransitions[this.state].includes(state)) {
this.state = state;
}
else {
throw new Error(`invalid transition from ${this.state} to ${state}`);
}
}
}
exports.StateMachine = StateMachine;
//# sourceMappingURL=state-machine.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../../../src/tasks/state-machine.ts"],"names":[],"mappings":";;;AAEA,MAAM,kBAAkB,GAAmC;IACzD,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC;IAC3C,MAAM,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC;IACnD,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC;IACtD,WAAW,EAAE,CAAC,WAAW,CAAC;CAC3B,CAAA;AAED,MAAa,YAAY;IACvB,KAAK,CAAY;IAEjB,YAAY,UAAqB,SAAS;QACxC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;IACvB,CAAC;IAED,WAAW,CAAC,KAAgB;QAC1B,IAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAC,CAAC;YACjD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,KAAK,OAAO,KAAK,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;CAEF;AAfD,oCAeC"}