@webtypen/webframez-core 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ import { ConsoleCommand } from "./ConsoleCommand";
2
+ export declare class QueueRunCommand extends ConsoleCommand {
3
+ static signature: string;
4
+ static description: string;
5
+ workerKey: string;
6
+ startTime: any;
7
+ currentJob: any;
8
+ jobTypes: any;
9
+ jobsExecuted: number;
10
+ jobsSucceeded: number;
11
+ jobsFailed: number;
12
+ lastJob: any;
13
+ log(message: string): void;
14
+ handle(): Promise<void>;
15
+ canStart(): boolean;
16
+ updateLock(): void;
17
+ random(min: number, max: number): number;
18
+ wait(ms: number): Promise<unknown>;
19
+ waitRun(): Promise<unknown>;
20
+ run(): Promise<void>;
21
+ }
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.QueueRunCommand = void 0;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const moment_1 = __importDefault(require("moment"));
18
+ const ConsoleCommand_1 = require("./ConsoleCommand");
19
+ const QueueJobsRegisty_1 = require("../Queue/QueueJobsRegisty");
20
+ const FileFunctions_1 = require("../Functions/FileFunctions");
21
+ const QueueJob_1 = require("../Queue/QueueJob");
22
+ const StringFunctions_1 = require("../Functions/StringFunctions");
23
+ class QueueRunCommand extends ConsoleCommand_1.ConsoleCommand {
24
+ constructor() {
25
+ super(...arguments);
26
+ // State
27
+ this.workerKey = "main";
28
+ this.startTime = null;
29
+ this.currentJob = null;
30
+ this.jobTypes = {};
31
+ // Statistics
32
+ this.jobsExecuted = 0;
33
+ this.jobsSucceeded = 0;
34
+ this.jobsFailed = 0;
35
+ this.lastJob = null;
36
+ }
37
+ log(message) {
38
+ this.writeln(`[${(0, moment_1.default)().format("YYYY-MM-DD HH:mm:ss")}] ${message}`);
39
+ }
40
+ handle() {
41
+ return __awaiter(this, void 0, void 0, function* () {
42
+ this.startTime = (0, moment_1.default)();
43
+ if (!this.canStart()) {
44
+ return;
45
+ }
46
+ this.log("Queue started");
47
+ yield this.run();
48
+ });
49
+ }
50
+ canStart() {
51
+ // Register Job-Types
52
+ for (let entry of QueueJobsRegisty_1.QueueJobsRegisty.getJobs()) {
53
+ this.jobTypes[entry.name] = entry;
54
+ }
55
+ const file = (0, FileFunctions_1.storageDir)("queue", `worker_${this.workerKey}.json`);
56
+ if (!fs_1.default.existsSync(file)) {
57
+ return true;
58
+ }
59
+ const json = JSON.parse(fs_1.default.readFileSync(file, "utf-8"));
60
+ if (!json || !json.updated_at) {
61
+ return true;
62
+ }
63
+ if (json.last_job) {
64
+ this.lastJob = json.last_job;
65
+ }
66
+ const diff = (0, moment_1.default)().diff((0, moment_1.default)(json.updated_at), "seconds");
67
+ if (diff && diff >= 30) {
68
+ return true;
69
+ }
70
+ return true;
71
+ this.writeln(`Worker '${this.workerKey}' is already running in Process${json.pid ? " " + json.pid : ""} (last update ${diff <= 180 ? diff + " seconds ago" : (diff / 60).toFixed(0) + " minutes ago"})`);
72
+ return false;
73
+ }
74
+ updateLock() {
75
+ if (!fs_1.default.existsSync((0, FileFunctions_1.storageDir)("queue"))) {
76
+ fs_1.default.mkdirSync((0, FileFunctions_1.storageDir)("queue"), { recursive: true });
77
+ }
78
+ fs_1.default.writeFileSync((0, FileFunctions_1.storageDir)("queue", `worker_${this.workerKey}.json`), JSON.stringify({
79
+ key: this.workerKey,
80
+ pid: process.pid,
81
+ started_at: this.startTime.format("YYYY-MM-DD HH:mm:ss"),
82
+ updated_at: (0, moment_1.default)().format("YYYY-MM-DD HH:mm:ss"),
83
+ current_job: this.currentJob
84
+ ? {
85
+ _id: this.currentJob._id,
86
+ started_at: this.currentJob.started_at,
87
+ }
88
+ : null,
89
+ last_job: this.lastJob,
90
+ }));
91
+ }
92
+ random(min, max) {
93
+ return Math.floor(Math.random() * (max - min + 1)) + min;
94
+ }
95
+ wait(ms) {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ return new Promise((resolve) => {
98
+ setTimeout(() => resolve(true), ms);
99
+ });
100
+ });
101
+ }
102
+ waitRun() {
103
+ return __awaiter(this, void 0, void 0, function* () {
104
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
105
+ this.updateLock();
106
+ yield this.wait(5000);
107
+ resolve(true);
108
+ }));
109
+ });
110
+ }
111
+ run() {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ while (true) {
114
+ this.updateLock();
115
+ const now = (0, moment_1.default)();
116
+ const check = yield QueueJob_1.QueueJob.aggregate([
117
+ {
118
+ $match: {
119
+ status: "pending",
120
+ jobclass: { $ne: null },
121
+ $or: [
122
+ {
123
+ start_at: null,
124
+ },
125
+ {
126
+ start_at: { $lte: now.toDate() },
127
+ },
128
+ ],
129
+ },
130
+ },
131
+ {
132
+ $limit: 1,
133
+ },
134
+ ]);
135
+ if (!check || !check[0] || !check[0]._id) {
136
+ this.currentJob = null;
137
+ yield this.waitRun();
138
+ continue;
139
+ }
140
+ yield this.wait(this.random(200, 600));
141
+ const job = yield QueueJob_1.QueueJob.where("_id", "=", check[0]._id).first();
142
+ if (!job || job.status !== "pending") {
143
+ this.currentJob = null;
144
+ yield this.waitRun();
145
+ continue;
146
+ }
147
+ this.log(`Start Job #${job.number} - ${job.jobclass}`);
148
+ const startedAt = new Date();
149
+ const executionKey = (0, moment_1.default)().format("YYYYMMDDHHmmss") + StringFunctions_1.StringFunctions.random(24);
150
+ job.status = "running";
151
+ job.started_at = startedAt;
152
+ job.ended_at = null;
153
+ job.executions = [
154
+ { key: executionKey, worker: this.workerKey, status: "running", started_at: startedAt },
155
+ ...(job.executions && job.executions.length > 0 ? job.executions : []),
156
+ ];
157
+ yield job.save();
158
+ this.currentJob = {
159
+ _id: job._id.toString(),
160
+ execution_key: executionKey,
161
+ started_at: startedAt,
162
+ job_executions_count: job.executions.length,
163
+ };
164
+ let jobInstance = null;
165
+ const jobType = this.jobTypes && this.jobTypes[job.jobclass] ? this.jobTypes[job.jobclass] : null;
166
+ try {
167
+ if (!jobType) {
168
+ throw new Error(`QueueError: Invalid job-type ${job.jobclass}`);
169
+ }
170
+ jobInstance = new jobType();
171
+ const result = yield jobInstance.handle(job);
172
+ const endedAt = new Date();
173
+ job.status = "finished";
174
+ job.ended_at = endedAt;
175
+ job.executions[0].status = "finished";
176
+ job.executions[0].ended_at = endedAt;
177
+ job.executions[0].log = jobInstance.getLog();
178
+ job.executions[0].duration_ms = (0, moment_1.default)().diff((0, moment_1.default)(startedAt), "milliseconds");
179
+ if (result && typeof result === "object" && result.__execute_again) {
180
+ job.status = "pending";
181
+ job.start_at = result.__execute_again;
182
+ }
183
+ yield job.save();
184
+ this.jobsSucceeded++;
185
+ this.log(`Finished Job #${job.number} - ${job.jobclass} - ${(job.executions[0].duration_ms / 1000).toFixed(2)}s`);
186
+ }
187
+ catch (e) {
188
+ const errorMessage = e instanceof Error ? e.stack || e.message : String(e);
189
+ const endedAt = new Date();
190
+ job.status = "failed";
191
+ job.ended_at = endedAt;
192
+ job.executions[0].status = "failed";
193
+ job.executions[0].ended_at = endedAt;
194
+ job.executions[0].error = errorMessage;
195
+ job.executions[0].duration_ms = (0, moment_1.default)().diff((0, moment_1.default)(startedAt), "milliseconds");
196
+ if (jobInstance) {
197
+ job.executions[0].log = jobInstance.getLog();
198
+ }
199
+ yield job.save();
200
+ this.jobsFailed++;
201
+ this.log(`Failed Job #${job.number} - ${job.jobclass} - ${(job.executions[0].duration_ms / 1000).toFixed(2)}s`);
202
+ }
203
+ this.jobsExecuted++;
204
+ this.currentJob = null;
205
+ yield this.waitRun();
206
+ }
207
+ });
208
+ }
209
+ }
210
+ exports.QueueRunCommand = QueueRunCommand;
211
+ // Command
212
+ QueueRunCommand.signature = "queue:run";
213
+ QueueRunCommand.description = "Run a queue worker";
@@ -13,6 +13,7 @@ exports.ConsoleApplication = void 0;
13
13
  const { Command } = require("commander");
14
14
  const Config_1 = require("./Config");
15
15
  const BuildFinishCommand_1 = require("./Commands/BuildFinishCommand");
16
+ const QueueJobsRegisty_1 = require("./Queue/QueueJobsRegisty");
16
17
  class ConsoleApplication {
17
18
  /**
18
19
  * Init the routes and start the http-server
@@ -60,6 +61,9 @@ class ConsoleApplication {
60
61
  program.addCommand(consoleCommand);
61
62
  }
62
63
  }
64
+ if (options.jobs && options.jobs.length > 0) {
65
+ QueueJobsRegisty_1.QueueJobsRegisty.registerJob(options.jobs);
66
+ }
63
67
  program.parse();
64
68
  }
65
69
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LambdaApplication = void 0;
4
4
  const Config_1 = require("./Config");
5
+ const QueueJobsRegisty_1 = require("./Queue/QueueJobsRegisty");
5
6
  const Router_1 = require("./Router/Router");
6
7
  class LambdaApplication {
7
8
  /**
@@ -17,7 +18,12 @@ class LambdaApplication {
17
18
  mode: "aws-lambda",
18
19
  kernel: options && options.kernel ? options.kernel : null,
19
20
  basename: options && options.basename ? options.basename : null,
21
+ routesFunction: options && options.routesFunction ? options.routesFunction : null,
22
+ tempDir: options && options.tempDir ? options.tempDir : null,
20
23
  });
24
+ if (options.jobs && options.jobs.length > 0) {
25
+ QueueJobsRegisty_1.QueueJobsRegisty.registerJob(options.jobs);
26
+ }
21
27
  return Router_1.Router.handleRequest(null, null, { event: event });
22
28
  }
23
29
  }
@@ -0,0 +1,12 @@
1
+ import { QueueJob } from "./QueueJob";
2
+ export declare class BaseQueueJob {
3
+ attempts: number;
4
+ currentLog: string;
5
+ handle(job: any): Promise<any>;
6
+ static create(props?: any): Promise<QueueJob>;
7
+ log(...args: any): this;
8
+ getLog(): string | null;
9
+ executeAgain(value?: number, unit?: "year" | "years" | "y" | "month" | "months" | "M" | "week" | "weeks" | "w" | "day" | "days" | "d" | "hour" | "hours" | "h" | "minute" | "minutes" | "m" | "second" | "seconds" | "s"): {
10
+ __execute_again: Date;
11
+ };
12
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BaseQueueJob = void 0;
16
+ const moment_1 = __importDefault(require("moment"));
17
+ const QueueJob_1 = require("./QueueJob");
18
+ class BaseQueueJob {
19
+ constructor() {
20
+ this.attempts = 3;
21
+ this.currentLog = "";
22
+ }
23
+ handle(job) {
24
+ return __awaiter(this, void 0, void 0, function* () { });
25
+ }
26
+ static create(props) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ const last = yield QueueJob_1.QueueJob.where("number", "=", { $gte: 0 }).orderBy("number", "DESC").first();
29
+ const job = new QueueJob_1.QueueJob();
30
+ job.number = last && last.number ? last.number + 1 : 1;
31
+ job.jobclass = this.name;
32
+ job.status = "pending";
33
+ job.created_at = new Date();
34
+ if (props) {
35
+ for (let key in props) {
36
+ job[key] = props[key];
37
+ }
38
+ }
39
+ yield job.save();
40
+ return job;
41
+ });
42
+ }
43
+ log(...args) {
44
+ let temp = "";
45
+ if (args) {
46
+ for (let entry of args) {
47
+ if (typeof entry === "object") {
48
+ try {
49
+ const str = JSON.stringify(entry);
50
+ if (str.length > 150 || temp.length > 150) {
51
+ temp += (temp && temp.trim() !== "" ? " " : "") + JSON.stringify(entry, null, 4);
52
+ }
53
+ else {
54
+ temp += (temp && temp.trim() !== "" ? " " : "") + str;
55
+ }
56
+ }
57
+ catch (e) {
58
+ temp += (temp && temp.trim() !== "" ? " " : "") + "[object Object]";
59
+ }
60
+ }
61
+ else {
62
+ temp += (temp && temp.trim() !== "" ? " " : "") + entry.toString();
63
+ }
64
+ }
65
+ }
66
+ this.currentLog += temp + "\n";
67
+ return this;
68
+ }
69
+ getLog() {
70
+ return this.currentLog && this.currentLog.trim() !== "" ? this.currentLog : null;
71
+ }
72
+ executeAgain(value = 30, unit = "seconds") {
73
+ return { __execute_again: (0, moment_1.default)().add(value, unit).toDate() };
74
+ }
75
+ }
76
+ exports.BaseQueueJob = BaseQueueJob;
@@ -0,0 +1,4 @@
1
+ import { Model } from "../Database/Model";
2
+ export declare class QueueJob extends Model {
3
+ __table: string;
4
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueueJob = void 0;
4
+ const Model_1 = require("../Database/Model");
5
+ class QueueJob extends Model_1.Model {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.__table = "queue_jobs";
9
+ }
10
+ }
11
+ exports.QueueJob = QueueJob;
@@ -0,0 +1,7 @@
1
+ declare class QueueJobsRegistyFacade {
2
+ private jobs;
3
+ getJobs(): any[];
4
+ registerJob(data: any): this;
5
+ }
6
+ export declare const QueueJobsRegisty: QueueJobsRegistyFacade;
7
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QueueJobsRegisty = void 0;
4
+ class QueueJobsRegistyFacade {
5
+ constructor() {
6
+ this.jobs = [];
7
+ }
8
+ getJobs() {
9
+ return this.jobs;
10
+ }
11
+ registerJob(data) {
12
+ if (Array.isArray(data)) {
13
+ for (let el of data) {
14
+ this.jobs.push(el);
15
+ }
16
+ }
17
+ else {
18
+ this.jobs.push(data);
19
+ }
20
+ return this;
21
+ }
22
+ }
23
+ exports.QueueJobsRegisty = new QueueJobsRegistyFacade();
@@ -7,6 +7,7 @@ exports.WebApplication = void 0;
7
7
  const http_1 = __importDefault(require("http"));
8
8
  const Config_1 = require("./Config");
9
9
  const Router_1 = require("./Router/Router");
10
+ const QueueJobsRegisty_1 = require("./Queue/QueueJobsRegisty");
10
11
  class WebApplication {
11
12
  /**
12
13
  * Init the routes and start the http-server
@@ -24,6 +25,9 @@ class WebApplication {
24
25
  routesFunction: options && options.routesFunction ? options.routesFunction : null,
25
26
  tempDir: options && options.tempDir ? options.tempDir : null,
26
27
  });
28
+ if (options.jobs && options.jobs.length > 0) {
29
+ QueueJobsRegisty_1.QueueJobsRegisty.registerJob(options.jobs);
30
+ }
27
31
  const port = options && options.port ? options.port : 3000;
28
32
  this.server = http_1.default.createServer((req, res) => {
29
33
  Router_1.Router.handleRequest(req, res);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",
@@ -28,6 +28,7 @@
28
28
  "@types/mime-types": "^2.1.4",
29
29
  "@types/node": "^18.11.9",
30
30
  "@types/qs": "^6.9.7",
31
+ "moment": "^2.30.1",
31
32
  "ts-node": "^10.9.1",
32
33
  "typescript": "^4.9.3"
33
34
  },
@@ -0,0 +1,221 @@
1
+ import fs from "fs";
2
+ import moment from "moment";
3
+ import { ConsoleCommand } from "./ConsoleCommand";
4
+ import { QueueJobsRegisty } from "../Queue/QueueJobsRegisty";
5
+ import { storageDir } from "../Functions/FileFunctions";
6
+ import { QueueJob } from "../Queue/QueueJob";
7
+ import { StringFunctions } from "../Functions/StringFunctions";
8
+
9
+ export class QueueRunCommand extends ConsoleCommand {
10
+ // Command
11
+ static signature = "queue:run";
12
+ static description = "Run a queue worker";
13
+
14
+ // State
15
+ workerKey = "main";
16
+ startTime: any = null;
17
+ currentJob: any = null;
18
+ jobTypes: any = {};
19
+
20
+ // Statistics
21
+ jobsExecuted = 0;
22
+ jobsSucceeded = 0;
23
+ jobsFailed = 0;
24
+ lastJob: any = null;
25
+
26
+ log(message: string) {
27
+ this.writeln(`[${moment().format("YYYY-MM-DD HH:mm:ss")}] ${message}`);
28
+ }
29
+
30
+ async handle() {
31
+ this.startTime = moment();
32
+ if (!this.canStart()) {
33
+ return;
34
+ }
35
+
36
+ this.log("Queue started");
37
+ await this.run();
38
+ }
39
+
40
+ canStart() {
41
+ // Register Job-Types
42
+ for (let entry of QueueJobsRegisty.getJobs()) {
43
+ this.jobTypes[entry.name] = entry;
44
+ }
45
+
46
+ const file = storageDir("queue", `worker_${this.workerKey}.json`);
47
+ if (!fs.existsSync(file)) {
48
+ return true;
49
+ }
50
+
51
+ const json = JSON.parse(fs.readFileSync(file, "utf-8"));
52
+ if (!json || !json.updated_at) {
53
+ return true;
54
+ }
55
+
56
+ if (json.last_job) {
57
+ this.lastJob = json.last_job;
58
+ }
59
+
60
+ const diff = moment().diff(moment(json.updated_at), "seconds");
61
+ if (diff && diff >= 30) {
62
+ return true;
63
+ }
64
+
65
+ return true;
66
+ this.writeln(
67
+ `Worker '${this.workerKey}' is already running in Process${json.pid ? " " + json.pid : ""} (last update ${
68
+ diff <= 180 ? diff + " seconds ago" : (diff / 60).toFixed(0) + " minutes ago"
69
+ })`
70
+ );
71
+ return false;
72
+ }
73
+
74
+ updateLock() {
75
+ if (!fs.existsSync(storageDir("queue"))) {
76
+ fs.mkdirSync(storageDir("queue"), { recursive: true });
77
+ }
78
+
79
+ fs.writeFileSync(
80
+ storageDir("queue", `worker_${this.workerKey}.json`),
81
+ JSON.stringify({
82
+ key: this.workerKey,
83
+ pid: process.pid,
84
+ started_at: this.startTime.format("YYYY-MM-DD HH:mm:ss"),
85
+ updated_at: moment().format("YYYY-MM-DD HH:mm:ss"),
86
+ current_job: this.currentJob
87
+ ? {
88
+ _id: this.currentJob._id,
89
+ started_at: this.currentJob.started_at,
90
+ }
91
+ : null,
92
+ last_job: this.lastJob,
93
+ })
94
+ );
95
+ }
96
+
97
+ random(min: number, max: number) {
98
+ return Math.floor(Math.random() * (max - min + 1)) + min;
99
+ }
100
+
101
+ async wait(ms: number) {
102
+ return new Promise((resolve) => {
103
+ setTimeout(() => resolve(true), ms);
104
+ });
105
+ }
106
+
107
+ async waitRun() {
108
+ return new Promise(async (resolve) => {
109
+ this.updateLock();
110
+ await this.wait(5000);
111
+ resolve(true);
112
+ });
113
+ }
114
+
115
+ async run() {
116
+ while (true) {
117
+ this.updateLock();
118
+
119
+ const now = moment();
120
+ const check = await QueueJob.aggregate([
121
+ {
122
+ $match: {
123
+ status: "pending",
124
+ jobclass: { $ne: null },
125
+ $or: [
126
+ {
127
+ start_at: null,
128
+ },
129
+ {
130
+ start_at: { $lte: now.toDate() },
131
+ },
132
+ ],
133
+ },
134
+ },
135
+ {
136
+ $limit: 1,
137
+ },
138
+ ]);
139
+
140
+ if (!check || !check[0] || !check[0]._id) {
141
+ this.currentJob = null;
142
+ await this.waitRun();
143
+ continue;
144
+ }
145
+
146
+ await this.wait(this.random(200, 600));
147
+ const job = await QueueJob.where("_id", "=", check[0]._id).first();
148
+ if (!job || job.status !== "pending") {
149
+ this.currentJob = null;
150
+ await this.waitRun();
151
+ continue;
152
+ }
153
+
154
+ this.log(`Start Job #${job.number} - ${job.jobclass}`);
155
+ const startedAt = new Date();
156
+ const executionKey = moment().format("YYYYMMDDHHmmss") + StringFunctions.random(24);
157
+ job.status = "running";
158
+ job.started_at = startedAt;
159
+ job.ended_at = null;
160
+ job.executions = [
161
+ { key: executionKey, worker: this.workerKey, status: "running", started_at: startedAt },
162
+ ...(job.executions && job.executions.length > 0 ? job.executions : []),
163
+ ];
164
+ await job.save();
165
+
166
+ this.currentJob = {
167
+ _id: job._id.toString(),
168
+ execution_key: executionKey,
169
+ started_at: startedAt,
170
+ job_executions_count: job.executions.length,
171
+ };
172
+
173
+ let jobInstance: any = null;
174
+ const jobType: any = this.jobTypes && this.jobTypes[job.jobclass] ? this.jobTypes[job.jobclass] : null;
175
+ try {
176
+ if (!jobType) {
177
+ throw new Error(`QueueError: Invalid job-type ${job.jobclass}`);
178
+ }
179
+
180
+ jobInstance = new jobType();
181
+ const result: any = await jobInstance.handle(job);
182
+
183
+ const endedAt = new Date();
184
+ job.status = "finished";
185
+ job.ended_at = endedAt;
186
+ job.executions[0].status = "finished";
187
+ job.executions[0].ended_at = endedAt;
188
+ job.executions[0].log = jobInstance.getLog();
189
+ job.executions[0].duration_ms = moment().diff(moment(startedAt), "milliseconds");
190
+
191
+ if (result && typeof result === "object" && result.__execute_again) {
192
+ job.status = "pending";
193
+ job.start_at = result.__execute_again;
194
+ }
195
+ await job.save();
196
+ this.jobsSucceeded++;
197
+ this.log(`Finished Job #${job.number} - ${job.jobclass} - ${(job.executions[0].duration_ms / 1000).toFixed(2)}s`);
198
+ } catch (e) {
199
+ const errorMessage = e instanceof Error ? e.stack || e.message : String(e);
200
+ const endedAt = new Date();
201
+ job.status = "failed";
202
+ job.ended_at = endedAt;
203
+ job.executions[0].status = "failed";
204
+ job.executions[0].ended_at = endedAt;
205
+ job.executions[0].error = errorMessage;
206
+ job.executions[0].duration_ms = moment().diff(moment(startedAt), "milliseconds");
207
+
208
+ if (jobInstance) {
209
+ job.executions[0].log = jobInstance.getLog();
210
+ }
211
+ await job.save();
212
+ this.jobsFailed++;
213
+ this.log(`Failed Job #${job.number} - ${job.jobclass} - ${(job.executions[0].duration_ms / 1000).toFixed(2)}s`);
214
+ }
215
+
216
+ this.jobsExecuted++;
217
+ this.currentJob = null;
218
+ await this.waitRun();
219
+ }
220
+ }
221
+ }
@@ -1,6 +1,7 @@
1
1
  const { Command } = require("commander");
2
2
  import { Config } from "./Config";
3
3
  import { BuildFinishCommand } from "./Commands/BuildFinishCommand";
4
+ import { QueueJobsRegisty } from "./Queue/QueueJobsRegisty";
4
5
 
5
6
  export class ConsoleApplication {
6
7
  /**
@@ -56,6 +57,10 @@ export class ConsoleApplication {
56
57
  }
57
58
  }
58
59
 
60
+ if (options.jobs && options.jobs.length > 0) {
61
+ QueueJobsRegisty.registerJob(options.jobs);
62
+ }
63
+
59
64
  program.parse();
60
65
  }
61
66
  }
@@ -1,23 +1,30 @@
1
1
  import { Config } from "./Config";
2
+ import { QueueJobsRegisty } from "./Queue/QueueJobsRegisty";
2
3
  import { Router } from "./Router/Router";
3
4
 
4
5
  export class LambdaApplication {
5
- /**
6
- * Init the routes and handle the request
7
- */
8
- boot(event: any, context: any, options?: any) {
9
- if (options && options.config) {
10
- for (let key in options.config) {
11
- Config.register(key, options.config[key]);
12
- }
13
- }
6
+ /**
7
+ * Init the routes and handle the request
8
+ */
9
+ boot(event: any, context: any, options?: any) {
10
+ if (options && options.config) {
11
+ for (let key in options.config) {
12
+ Config.register(key, options.config[key]);
13
+ }
14
+ }
15
+
16
+ Router.init({
17
+ mode: "aws-lambda",
18
+ kernel: options && options.kernel ? options.kernel : null,
19
+ basename: options && options.basename ? options.basename : null,
20
+ routesFunction: options && options.routesFunction ? options.routesFunction : null,
21
+ tempDir: options && options.tempDir ? options.tempDir : null,
22
+ });
14
23
 
15
- Router.init({
16
- mode: "aws-lambda",
17
- kernel: options && options.kernel ? options.kernel : null,
18
- basename: options && options.basename ? options.basename : null,
19
- });
24
+ if (options.jobs && options.jobs.length > 0) {
25
+ QueueJobsRegisty.registerJob(options.jobs);
26
+ }
20
27
 
21
- return Router.handleRequest(null, null, { event: event });
22
- }
28
+ return Router.handleRequest(null, null, { event: event });
29
+ }
23
30
  }
@@ -0,0 +1,82 @@
1
+ import moment from "moment";
2
+ import { QueueJob } from "./QueueJob";
3
+
4
+ export class BaseQueueJob {
5
+ attempts: number = 3;
6
+ currentLog = "";
7
+
8
+ async handle(job: any): Promise<any> {}
9
+
10
+ static async create(props?: any) {
11
+ const last = await QueueJob.where("number", "=", { $gte: 0 }).orderBy("number", "DESC").first();
12
+
13
+ const job = new QueueJob();
14
+ job.number = last && last.number ? last.number + 1 : 1;
15
+ job.jobclass = this.name;
16
+ job.status = "pending";
17
+ job.created_at = new Date();
18
+ if (props) {
19
+ for (let key in props) {
20
+ job[key] = props[key];
21
+ }
22
+ }
23
+ await job.save();
24
+ return job;
25
+ }
26
+
27
+ log(...args: any) {
28
+ let temp = "";
29
+ if (args) {
30
+ for (let entry of args) {
31
+ if (typeof entry === "object") {
32
+ try {
33
+ const str = JSON.stringify(entry);
34
+ if (str.length > 150 || temp.length > 150) {
35
+ temp += (temp && temp.trim() !== "" ? " " : "") + JSON.stringify(entry, null, 4);
36
+ } else {
37
+ temp += (temp && temp.trim() !== "" ? " " : "") + str;
38
+ }
39
+ } catch (e) {
40
+ temp += (temp && temp.trim() !== "" ? " " : "") + "[object Object]";
41
+ }
42
+ } else {
43
+ temp += (temp && temp.trim() !== "" ? " " : "") + entry.toString();
44
+ }
45
+ }
46
+ }
47
+ this.currentLog += temp + "\n";
48
+ return this;
49
+ }
50
+
51
+ getLog() {
52
+ return this.currentLog && this.currentLog.trim() !== "" ? this.currentLog : null;
53
+ }
54
+
55
+ executeAgain(
56
+ value: number = 30,
57
+ unit:
58
+ | "year"
59
+ | "years"
60
+ | "y"
61
+ | "month"
62
+ | "months"
63
+ | "M"
64
+ | "week"
65
+ | "weeks"
66
+ | "w"
67
+ | "day"
68
+ | "days"
69
+ | "d"
70
+ | "hour"
71
+ | "hours"
72
+ | "h"
73
+ | "minute"
74
+ | "minutes"
75
+ | "m"
76
+ | "second"
77
+ | "seconds"
78
+ | "s" = "seconds"
79
+ ) {
80
+ return { __execute_again: moment().add(value, unit).toDate() };
81
+ }
82
+ }
@@ -0,0 +1,5 @@
1
+ import { Model } from "../Database/Model";
2
+
3
+ export class QueueJob extends Model {
4
+ __table = "queue_jobs";
5
+ }
@@ -0,0 +1,20 @@
1
+ class QueueJobsRegistyFacade {
2
+ private jobs: any[] = [];
3
+
4
+ getJobs() {
5
+ return this.jobs;
6
+ }
7
+
8
+ registerJob(data: any) {
9
+ if (Array.isArray(data)) {
10
+ for (let el of data) {
11
+ this.jobs.push(el);
12
+ }
13
+ } else {
14
+ this.jobs.push(data);
15
+ }
16
+ return this;
17
+ }
18
+ }
19
+
20
+ export const QueueJobsRegisty = new QueueJobsRegistyFacade();
@@ -1,6 +1,7 @@
1
1
  import http, { Server } from "http";
2
2
  import { Config } from "./Config";
3
3
  import { Router } from "./Router/Router";
4
+ import { QueueJobsRegisty } from "./Queue/QueueJobsRegisty";
4
5
 
5
6
  export class WebApplication {
6
7
  private server!: Server;
@@ -23,6 +24,10 @@ export class WebApplication {
23
24
  tempDir: options && options.tempDir ? options.tempDir : null,
24
25
  });
25
26
 
27
+ if (options.jobs && options.jobs.length > 0) {
28
+ QueueJobsRegisty.registerJob(options.jobs);
29
+ }
30
+
26
31
  const port = options && options.port ? options.port : 3000;
27
32
  this.server = http.createServer((req, res) => {
28
33
  Router.handleRequest(req, res);