@triophore/falconjs 1.0.0
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/FalconAuthPlugin.js +473 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/core/auth.js +200 -0
- package/core/cache/redis_cacher.js +7 -0
- package/core/check_collection.js +9 -0
- package/core/crypto/encrypt_decrypt.js +19 -0
- package/core/errors.js +48 -0
- package/core/logger/log4js.js +89 -0
- package/core/logo.js +3 -0
- package/core/mongo/generateModelfromJsonFile.js +128 -0
- package/core/mongo/mongoSchmeFromJson.js +90 -0
- package/core/parse_num.js +8 -0
- package/core/rannum.js +33 -0
- package/core/ranstring.js +33 -0
- package/core/recursive-require-call.js +121 -0
- package/core/uitls/mongoose_to_joi.js +72 -0
- package/core/uitls/return.js +7 -0
- package/falcon.js +1644 -0
- package/falconAuthPlugin.js +17 -0
- package/falconBaseService.js +532 -0
- package/falconBaseWorker.js +540 -0
- package/index.js +4 -0
- package/out/Falcon.html +777 -0
- package/out/falcon.js.html +525 -0
- package/out/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/out/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/out/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Light-webfont.eot +0 -0
- package/out/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Light-webfont.woff +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/out/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/out/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/out/index.html +65 -0
- package/out/scripts/linenumber.js +25 -0
- package/out/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/out/scripts/prettify/lang-css.js +2 -0
- package/out/scripts/prettify/prettify.js +28 -0
- package/out/styles/jsdoc-default.css +358 -0
- package/out/styles/prettify-jsdoc.css +111 -0
- package/out/styles/prettify-tomorrow.css +132 -0
- package/package.json +106 -0
- package/settings.js +1 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
require('dotenv').config({ quiet: true, debug: false });
|
|
2
|
+
const mqtt = require("mqtt");
|
|
3
|
+
const config = process.env;
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
6
|
+
const bcrypt = require('bcrypt');
|
|
7
|
+
const mongoosePaginate = require('mongoose-paginate-v2');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const JWT = require('jsonwebtoken');
|
|
11
|
+
const { v4: uuidv4 } = require('uuid');
|
|
12
|
+
const redis = require("redis");
|
|
13
|
+
const axios = require("axios");
|
|
14
|
+
const { ValidationError, DatabaseError } = require('./core/errors');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base class for Falcon workers that process background tasks and jobs.
|
|
18
|
+
* Workers are typically short-lived processes that handle specific tasks like image processing,
|
|
19
|
+
* data transformation, or batch operations. They can be spawned on-demand or run continuously.
|
|
20
|
+
*
|
|
21
|
+
* @class FalconBaseWorker
|
|
22
|
+
* @example
|
|
23
|
+
* class ImageProcessor extends FalconBaseWorker {
|
|
24
|
+
* constructor() {
|
|
25
|
+
* super('image-processor');
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* async onMessage(topic, msg) {
|
|
29
|
+
* if (topic === 'worker_image-processor_job') {
|
|
30
|
+
* const job = JSON.parse(msg);
|
|
31
|
+
* await this.processImage(job);
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* async run() {
|
|
36
|
+
* const args = this.parseArgs();
|
|
37
|
+
* if (args.jobId) {
|
|
38
|
+
* await this.processJob(args.jobId);
|
|
39
|
+
* process.exit(0); // Exit after single job
|
|
40
|
+
* }
|
|
41
|
+
* // Otherwise wait for MQTT jobs
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* const worker = new ImageProcessor();
|
|
46
|
+
* worker.init();
|
|
47
|
+
*/
|
|
48
|
+
class FalconBaseWorker {
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a new Falcon worker instance.
|
|
52
|
+
* Automatically loads settings, parses command line arguments, and sets up graceful shutdown.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} workerId - Worker identifier used for MQTT topics and logging
|
|
55
|
+
* @throws {Error} Throws if worker ID is not provided
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* class MyWorker extends FalconBaseWorker {
|
|
59
|
+
* constructor() {
|
|
60
|
+
* super('my-worker'); // Creates topics: worker_my-worker, worker_my-worker_job, worker_my-worker_ping
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
constructor(workerId) {
|
|
65
|
+
if (!workerId || typeof workerId !== 'string') {
|
|
66
|
+
throw new ValidationError('workerId must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.workerId = workerId;
|
|
70
|
+
this.models = {};
|
|
71
|
+
this.CONTEXT = { models: {} };
|
|
72
|
+
this.db = null;
|
|
73
|
+
this.mqttClient = null;
|
|
74
|
+
this.redisClient = null;
|
|
75
|
+
this.env = process.env;
|
|
76
|
+
this.args = process.argv.slice(2);
|
|
77
|
+
|
|
78
|
+
// Load settings from CWD
|
|
79
|
+
try {
|
|
80
|
+
const settingsPath = path.join(process.cwd(), 'settings.js');
|
|
81
|
+
if (fs.existsSync(settingsPath)) {
|
|
82
|
+
this.SETTINGS = require(settingsPath).settings;
|
|
83
|
+
} else {
|
|
84
|
+
console.warn("Settings file not found in CWD");
|
|
85
|
+
this.SETTINGS = { models: [] };
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
console.error("Error loading settings:", e);
|
|
89
|
+
this.SETTINGS = { models: [] };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.setupGracefulShutdown();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks if running in development mode.
|
|
97
|
+
* @private
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
#isDev() {
|
|
101
|
+
return process.env.MODE === "DEV";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parses command line arguments passed to the worker process.
|
|
106
|
+
* Supports both base64 encoded JSON (preferred) and simple key=value pairs.
|
|
107
|
+
* The main Falcon server uses base64 JSON when spawning workers.
|
|
108
|
+
*
|
|
109
|
+
* @returns {Object} Parsed arguments object
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Base64 JSON (from Falcon server):
|
|
113
|
+
* // node worker.js eyJqb2JJZCI6MTIzLCJ0eXBlIjoiaW1hZ2UifQ==
|
|
114
|
+
* const args = this.parseArgs(); // { jobId: 123, type: "image" }
|
|
115
|
+
*
|
|
116
|
+
* // Key=value pairs (manual execution):
|
|
117
|
+
* // node worker.js jobId=123 type=image
|
|
118
|
+
* const args = this.parseArgs(); // { jobId: "123", type: "image" }
|
|
119
|
+
*
|
|
120
|
+
* // No arguments:
|
|
121
|
+
* const args = this.parseArgs(); // {}
|
|
122
|
+
*/
|
|
123
|
+
parseArgs() {
|
|
124
|
+
if (this.args.length === 0) return {};
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Try to parse as base64 encoded JSON first
|
|
128
|
+
const decoded = Buffer.from(this.args[0], 'base64').toString('utf8');
|
|
129
|
+
return JSON.parse(decoded);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// Fallback to simple key=value parsing
|
|
132
|
+
const parsed = {};
|
|
133
|
+
this.args.forEach(arg => {
|
|
134
|
+
const [key, value] = arg.split('=');
|
|
135
|
+
if (key && value) {
|
|
136
|
+
parsed[key] = value;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Loads a model by name from various possible locations.
|
|
145
|
+
* Searches in: models/mongo/, models/, workers/models/
|
|
146
|
+
*
|
|
147
|
+
* @private
|
|
148
|
+
* @param {string} name - Model name without .js extension
|
|
149
|
+
* @throws {Error} Logs error if model loading fails but doesn't throw
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* // Searches for job.js in:
|
|
153
|
+
* // - /project/models/mongo/job.js
|
|
154
|
+
* // - /project/models/job.js
|
|
155
|
+
* // - /project/workers/models/job.js
|
|
156
|
+
* await this.#load_model_name('job');
|
|
157
|
+
*/
|
|
158
|
+
async #load_model_name(name) {
|
|
159
|
+
const cwd = process.cwd();
|
|
160
|
+
const potentialPaths = [
|
|
161
|
+
path.join(cwd, "models", "mongo", name + ".js"),
|
|
162
|
+
path.join(cwd, "models", name + ".js"),
|
|
163
|
+
path.join(cwd, "workers", "models", name + ".js")
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
let modelPath = null;
|
|
167
|
+
for (const p of potentialPaths) {
|
|
168
|
+
if (fs.existsSync(p)) {
|
|
169
|
+
modelPath = p;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (modelPath) {
|
|
175
|
+
try {
|
|
176
|
+
const _temp = await require(modelPath)(mongoose);
|
|
177
|
+
this.models[name] = _temp;
|
|
178
|
+
this.CONTEXT["models"][name] = _temp;
|
|
179
|
+
console.info(`${name} model registered from ${modelPath}`);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error(`Failed to load model ${name} from ${modelPath}:`, e);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
console.warn(`Model ${name} not found in any standard location.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Connects to MongoDB database.
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
async #startMongoDB() {
|
|
193
|
+
if (!config.MONGODB_URL) {
|
|
194
|
+
console.warn("MONGODB_URL not configured, skipping database connection");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.#isDev()) {
|
|
199
|
+
mongoose.set("debug", true);
|
|
200
|
+
console.debug("Mongoose ODM is set in Debug Mode");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await mongoose.connect(config.MONGODB_URL, {});
|
|
205
|
+
mongoose.set("debug", (collectionName, method, query, doc) => {
|
|
206
|
+
console.info(`MONGOOSE ==> ${collectionName}.${method}`, JSON.stringify(query), doc);
|
|
207
|
+
});
|
|
208
|
+
this.db = mongoose.connection.db;
|
|
209
|
+
this.CONTEXT["db"] = this.db;
|
|
210
|
+
this.CONTEXT["mongoose"] = mongoose;
|
|
211
|
+
console.info("MongoDB connected");
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("MongoDB connection failed:", error);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Connects to Redis if enabled.
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
async #startRedis() {
|
|
223
|
+
if (config.REDIS_ENABLE === "true" && config.REDIS_URL) {
|
|
224
|
+
try {
|
|
225
|
+
this.redis_client = await redis.createClient({ url: config.REDIS_URL }).connect();
|
|
226
|
+
this.CONTEXT["redis"] = this.redis_client;
|
|
227
|
+
console.info("Redis connected");
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("Redis connection failed:", error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Connects to MQTT broker.
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
async #startMQTT() {
|
|
239
|
+
if (!config.MQTT_URL) {
|
|
240
|
+
console.warn("MQTT_URL not configured, skipping MQTT connection");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
this.mqtt_client = mqtt.connect(config.MQTT_URL);
|
|
246
|
+
this.mqtt_client.on("connect", this.onConnect.bind(this));
|
|
247
|
+
this.mqtt_client.on("message", this.#onMessage.bind(this));
|
|
248
|
+
this.mqtt_client.on("error", (err) => {
|
|
249
|
+
console.error("MQTT connection error:", err);
|
|
250
|
+
});
|
|
251
|
+
this.CONTEXT["mqtt_client"] = this.mqtt_client;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error("MQTT connection failed:", error);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Loads all configured models.
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
async #loadModels() {
|
|
263
|
+
if (!this.SETTINGS.models || this.SETTINGS.models.length === 0) {
|
|
264
|
+
console.info("No models configured");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.info("-----------------Registering Models----------------");
|
|
269
|
+
for (const model of this.SETTINGS.models) {
|
|
270
|
+
await this.#load_model_name(model);
|
|
271
|
+
}
|
|
272
|
+
console.info("-----------------Models Registered----------------");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Publishes data to websocket clients via MQTT.
|
|
277
|
+
* @param {*} data - Data to broadcast
|
|
278
|
+
*/
|
|
279
|
+
publishToWebsocket(data) {
|
|
280
|
+
if (this.mqttClient) {
|
|
281
|
+
this.mqttClient.publish('websocket_broadcast', JSON.stringify(data));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Emits data to a specific WebSocket room/group via MQTT.
|
|
287
|
+
*
|
|
288
|
+
* @param {string} room - Room name to emit to
|
|
289
|
+
* @param {string} event - Event name
|
|
290
|
+
* @param {*} payload - Data payload
|
|
291
|
+
*/
|
|
292
|
+
emitToGroup(room, event, payload) {
|
|
293
|
+
if (this.mqttClient) {
|
|
294
|
+
this.mqttClient.publish('websocket_emit_group', JSON.stringify({
|
|
295
|
+
room,
|
|
296
|
+
event,
|
|
297
|
+
payload
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Emits data to a specific WebSocket client by socket ID via MQTT.
|
|
304
|
+
*
|
|
305
|
+
* @param {string} socketId - Socket ID to emit to
|
|
306
|
+
* @param {string} event - Event name
|
|
307
|
+
* @param {*} payload - Data payload
|
|
308
|
+
*/
|
|
309
|
+
emitToSocket(socketId, event, payload) {
|
|
310
|
+
if (this.mqttClient) {
|
|
311
|
+
this.mqttClient.publish('websocket_emit_socket', JSON.stringify({
|
|
312
|
+
socketId,
|
|
313
|
+
event,
|
|
314
|
+
payload
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Publishes data to a specific MQTT topic.
|
|
321
|
+
* Supports both string and object data (objects are JSON stringified).
|
|
322
|
+
*
|
|
323
|
+
* @async
|
|
324
|
+
* @param {string} topic - MQTT topic to publish to
|
|
325
|
+
* @param {*} msg - Message to publish (strings sent as-is, objects JSON stringified)
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* // Send job completion status
|
|
329
|
+
* await this.publish('worker_image-processor_complete', {
|
|
330
|
+
* jobId: 123,
|
|
331
|
+
* status: 'completed',
|
|
332
|
+
* result: { processedPath: '/images/processed/123.jpg' }
|
|
333
|
+
* });
|
|
334
|
+
*
|
|
335
|
+
* // Send simple string message
|
|
336
|
+
* await this.publish('worker_status', 'ready');
|
|
337
|
+
*/
|
|
338
|
+
async publish(topic, msg) {
|
|
339
|
+
if (this.mqtt_client) {
|
|
340
|
+
const message = typeof msg === 'string' ? msg : JSON.stringify(msg);
|
|
341
|
+
await this.mqtt_client.publish(topic, message);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Called when MQTT connection is established.
|
|
347
|
+
* Automatically subscribes to worker-specific topics for jobs and health checks.
|
|
348
|
+
* Override in subclasses to add custom subscriptions or initialization logic.
|
|
349
|
+
*
|
|
350
|
+
* @async
|
|
351
|
+
* @virtual
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* async onConnect() {
|
|
355
|
+
* await super.onConnect(); // Call parent to get default subscriptions
|
|
356
|
+
* await this.mqtt_client.subscribe('worker_image-processor_priority');
|
|
357
|
+
* console.log('Image processor worker connected');
|
|
358
|
+
* }
|
|
359
|
+
*/
|
|
360
|
+
async onConnect() {
|
|
361
|
+
console.info(`Worker ${this.service_id} connected to MQTT`);
|
|
362
|
+
// Subscribe to worker-specific topics
|
|
363
|
+
await this.mqtt_client.subscribe(`worker_${this.service_id}`);
|
|
364
|
+
await this.mqtt_client.subscribe(`worker_${this.service_id}_ping`);
|
|
365
|
+
await this.mqtt_client.subscribe(`worker_${this.service_id}_job`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Internal MQTT message handler.
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
async #onMessage(topic, msg) {
|
|
373
|
+
// Handle ping/pong for health checks
|
|
374
|
+
if (topic === `worker_${this.service_id}_ping`) {
|
|
375
|
+
await this.publish(`worker_${this.service_id}_pong`, 'pong');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await this.onMessage(topic, msg);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Override this method to handle incoming MQTT messages and job requests.
|
|
384
|
+
* Called for all subscribed topics except ping messages (handled automatically).
|
|
385
|
+
*
|
|
386
|
+
* @async
|
|
387
|
+
* @virtual
|
|
388
|
+
* @param {string} topic - MQTT topic that received the message
|
|
389
|
+
* @param {Buffer} msg - Raw message buffer (use msg.toString() to get string)
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* async onMessage(topic, msg) {
|
|
393
|
+
* const data = JSON.parse(msg.toString());
|
|
394
|
+
*
|
|
395
|
+
* switch (topic) {
|
|
396
|
+
* case 'worker_image-processor_job':
|
|
397
|
+
* await this.processImage(data);
|
|
398
|
+
* break;
|
|
399
|
+
* case 'worker_image-processor_priority':
|
|
400
|
+
* await this.processPriorityJob(data);
|
|
401
|
+
* break;
|
|
402
|
+
* }
|
|
403
|
+
* }
|
|
404
|
+
*/
|
|
405
|
+
async onMessage(topic, msg) {
|
|
406
|
+
// Override in subclasses
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Override this method with your worker's main processing logic.
|
|
411
|
+
* Called after all initialization (database, MQTT, models) is complete.
|
|
412
|
+
* Can handle both one-time jobs (exit after completion) and continuous processing.
|
|
413
|
+
*
|
|
414
|
+
* @async
|
|
415
|
+
* @virtual
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* // One-time job worker
|
|
419
|
+
* async run() {
|
|
420
|
+
* const args = this.parseArgs();
|
|
421
|
+
* if (args.jobId) {
|
|
422
|
+
* await this.processJob(args.jobId);
|
|
423
|
+
* process.exit(0); // Exit after processing
|
|
424
|
+
* }
|
|
425
|
+
* }
|
|
426
|
+
*
|
|
427
|
+
* // Continuous worker
|
|
428
|
+
* async run() {
|
|
429
|
+
* console.log('Worker ready for jobs...');
|
|
430
|
+
* // Wait for MQTT messages, process jobs as they come
|
|
431
|
+
* }
|
|
432
|
+
*/
|
|
433
|
+
async run() {
|
|
434
|
+
// Override in subclasses
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Initializes the worker with all dependencies in the correct order.
|
|
439
|
+
* Sets up MongoDB, Redis, models, MQTT connection, and then calls run().
|
|
440
|
+
* This method should be called to start the worker.
|
|
441
|
+
*
|
|
442
|
+
* @async
|
|
443
|
+
* @throws {Error} Throws if any initialization step fails
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* const imageWorker = new ImageProcessor();
|
|
447
|
+
* await imageWorker.init(); // Worker is now running
|
|
448
|
+
*/
|
|
449
|
+
async init() {
|
|
450
|
+
console.info(`Initializing worker: ${this.service_id}`);
|
|
451
|
+
await this.#startMongoDB();
|
|
452
|
+
await this.#startRedis();
|
|
453
|
+
await this.#loadModels();
|
|
454
|
+
await this.#startMQTT();
|
|
455
|
+
await this.run();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Starts the worker (alias for run).
|
|
460
|
+
*/
|
|
461
|
+
async start() {
|
|
462
|
+
await this.run();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Gracefully cleans up all worker resources.
|
|
467
|
+
* Closes MQTT connection, Redis connection, and MongoDB connection in that order.
|
|
468
|
+
* Called automatically during graceful shutdown or can be called manually.
|
|
469
|
+
*
|
|
470
|
+
* @async
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* // Manual cleanup before exit
|
|
474
|
+
* await worker.cleanup();
|
|
475
|
+
* process.exit(0);
|
|
476
|
+
*
|
|
477
|
+
* // Automatic cleanup on process signals (already set up in constructor)
|
|
478
|
+
* process.on('SIGTERM', async () => {
|
|
479
|
+
* await worker.cleanup();
|
|
480
|
+
* process.exit(0);
|
|
481
|
+
* });
|
|
482
|
+
*/
|
|
483
|
+
async cleanup() {
|
|
484
|
+
console.info(`Cleaning up worker: ${this.service_id}`);
|
|
485
|
+
|
|
486
|
+
if (this.mqtt_client && this.mqtt_client.connected) {
|
|
487
|
+
await this.mqtt_client.end();
|
|
488
|
+
this.mqtt_client = null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (this.redis_client && this.redis_client.isOpen) {
|
|
492
|
+
await this.redis_client.quit();
|
|
493
|
+
this.redis_client = null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (this.db && mongoose.connection.readyState === 1) {
|
|
497
|
+
await mongoose.connection.close();
|
|
498
|
+
this.db = null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Sets up graceful shutdown handlers for process signals and uncaught exceptions.
|
|
504
|
+
* Automatically handles SIGTERM, SIGINT, uncaughtException, and unhandledRejection.
|
|
505
|
+
* Called automatically in constructor - no need to call manually.
|
|
506
|
+
*
|
|
507
|
+
* @private
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* // Automatically handles these scenarios:
|
|
511
|
+
* // - Parent process termination (SIGTERM)
|
|
512
|
+
* // - Ctrl+C in terminal (SIGINT)
|
|
513
|
+
* // - Unhandled promise rejections
|
|
514
|
+
* // - Uncaught exceptions
|
|
515
|
+
*
|
|
516
|
+
* // All trigger cleanup() before process exit
|
|
517
|
+
*/
|
|
518
|
+
setupGracefulShutdown() {
|
|
519
|
+
const shutdown = async (signal) => {
|
|
520
|
+
console.log(`Worker ${this.service_id} received ${signal}, shutting down...`);
|
|
521
|
+
await this.cleanup();
|
|
522
|
+
process.exit(0);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
526
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
527
|
+
process.on('uncaughtException', async (err) => {
|
|
528
|
+
console.error('Uncaught Exception:', err);
|
|
529
|
+
await this.cleanup();
|
|
530
|
+
process.exit(1);
|
|
531
|
+
});
|
|
532
|
+
process.on('unhandledRejection', async (reason) => {
|
|
533
|
+
console.error('Unhandled Rejection:', reason);
|
|
534
|
+
await this.cleanup();
|
|
535
|
+
process.exit(1);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
module.exports = FalconBaseWorker;
|
package/index.js
ADDED