@sockethub/data-layer 1.0.0-alpha.10

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,97 @@
1
+ import { Worker } from "bullmq";
2
+ import debug, { type Debugger } from "debug";
3
+
4
+ import { JobBase, createIORedisConnection } from "./job-base.js";
5
+ import type { JobEncrypted, JobHandler, RedisConfig } from "./types.js";
6
+
7
+ /**
8
+ * Worker for processing jobs from a Redis queue within platform child processes.
9
+ *
10
+ * Connects to the same queue as its corresponding JobQueue instance and processes
11
+ * jobs using a platform-specific handler function. Provides automatic decryption
12
+ * of job data and error handling.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const worker = new JobWorker('irc-platform', 'session123', secret, redisConfig);
17
+ * worker.onJob(async (job) => {
18
+ * // Process the decrypted ActivityStreams message
19
+ * return await processMessage(job.msg);
20
+ * });
21
+ * ```
22
+ */
23
+ export class JobWorker extends JobBase {
24
+ readonly uid: string;
25
+ protected worker: Worker;
26
+ protected handler: JobHandler;
27
+ private readonly debug: Debugger;
28
+ private readonly redisConfig: RedisConfig;
29
+ private readonly queueId: string;
30
+ private initialized = false;
31
+
32
+ /**
33
+ * Creates a new JobWorker instance.
34
+ *
35
+ * @param instanceId - Must match the instanceId of the corresponding JobQueue
36
+ * @param sessionId - Must match the sessionId of the corresponding JobQueue
37
+ * @param secret - 32-character encryption secret, must match JobQueue secret
38
+ * @param redisConfig - Redis connection configuration
39
+ */
40
+ constructor(
41
+ instanceId: string,
42
+ sessionId: string,
43
+ secret: string,
44
+ redisConfig: RedisConfig,
45
+ ) {
46
+ super(secret);
47
+ this.uid = `sockethub:data-layer:worker:${instanceId}:${sessionId}`;
48
+ this.queueId = `sockethub:data-layer:queue:${instanceId}:${sessionId}`;
49
+ this.debug = debug(this.uid);
50
+ this.redisConfig = redisConfig;
51
+ }
52
+
53
+ protected init() {
54
+ if (this.initialized) {
55
+ throw new Error(`JobWorker already initialized for ${this.uid}`);
56
+ }
57
+ this.initialized = true;
58
+ // BullMQ v5+ prohibits colons in queue names, so replace with dashes
59
+ // while keeping queueId with colons for debug namespace convention
60
+ const queueName = this.queueId.replace(/:/g, "-");
61
+ // Let BullMQ create its own connection for better lifecycle management
62
+ this.worker = new Worker(queueName, this.jobHandler.bind(this), {
63
+ connection: this.redisConfig,
64
+ // Prevent infinite retry loops when platform child process crashes mid-job.
65
+ // If worker disappears (crash/disconnect), job becomes "stalled" and retries
66
+ // up to maxStalledCount times (with default 30s interval) before failing permanently.
67
+ maxStalledCount: 3,
68
+ });
69
+ this.debug("initialized");
70
+ }
71
+
72
+ /**
73
+ * Registers a job handler function and starts processing jobs from the queue.
74
+ *
75
+ * @param handler - Function that processes decrypted job data and returns results
76
+ */
77
+ onJob(handler: JobHandler): void {
78
+ this.handler = handler;
79
+ this.init();
80
+ }
81
+
82
+ /**
83
+ * Gracefully shuts down the worker, stopping job processing and cleaning up connections.
84
+ */
85
+ async shutdown() {
86
+ await this.worker.pause();
87
+ this.removeAllListeners();
88
+ this.worker.removeAllListeners();
89
+ await this.worker.close();
90
+ }
91
+
92
+ protected async jobHandler(encryptedJob: JobEncrypted) {
93
+ const job = this.decryptJobData(encryptedJob);
94
+ this.debug(`handling ${job.title} ${job.msg.type}`);
95
+ return await this.handler(job);
96
+ }
97
+ }
package/src/types.ts ADDED
@@ -0,0 +1,35 @@
1
+ import type {
2
+ ActivityStream,
3
+ InternalActivityStream,
4
+ } from "@sockethub/schemas";
5
+
6
+ export type RedisConfig = {
7
+ url: string;
8
+ };
9
+
10
+ export interface JobDataEncrypted {
11
+ title?: string;
12
+ msg: string;
13
+ sessionId: string;
14
+ }
15
+
16
+ export interface JobDataDecrypted {
17
+ title?: string;
18
+ msg: InternalActivityStream;
19
+ sessionId: string;
20
+ }
21
+
22
+ export interface JobEncrypted {
23
+ data: JobDataEncrypted;
24
+ remove?: () => void;
25
+ }
26
+
27
+ export interface JobDecrypted {
28
+ data: JobDataDecrypted;
29
+ remove?: () => void;
30
+ returnvalue: unknown;
31
+ }
32
+
33
+ export type JobHandler = (
34
+ job: JobDataDecrypted,
35
+ ) => Promise<string | undefined | ActivityStream>;