@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.
- package/dist/Commands/QueueRunCommand.d.ts +21 -0
- package/dist/Commands/QueueRunCommand.js +213 -0
- package/dist/ConsoleApplication.js +4 -0
- package/dist/LambdaApplication.js +6 -0
- package/dist/Queue/BaseQueueJob.d.ts +12 -0
- package/dist/Queue/BaseQueueJob.js +76 -0
- package/dist/Queue/QueueJob.d.ts +4 -0
- package/dist/Queue/QueueJob.js +11 -0
- package/dist/Queue/QueueJobsRegisty.d.ts +7 -0
- package/dist/Queue/QueueJobsRegisty.js +23 -0
- package/dist/WebApplication.js +4 -0
- package/package.json +2 -1
- package/src/Commands/QueueRunCommand.ts +221 -0
- package/src/ConsoleApplication.ts +5 -0
- package/src/LambdaApplication.ts +23 -16
- package/src/Queue/BaseQueueJob.ts +82 -0
- package/src/Queue/QueueJob.ts +5 -0
- package/src/Queue/QueueJobsRegisty.ts +20 -0
- package/src/WebApplication.ts +5 -0
|
@@ -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,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,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();
|
package/dist/WebApplication.js
CHANGED
|
@@ -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.
|
|
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
|
}
|
package/src/LambdaApplication.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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,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();
|
package/src/WebApplication.ts
CHANGED
|
@@ -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);
|