@superdangerous/app-framework 4.9.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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Queue Service for background job processing
3
+ * Based on patterns from mila-ai but enhanced with TypeScript and better error handling
4
+ */
5
+
6
+ import { EventEmitter } from "events";
7
+ import { createLogger } from "../core/index.js";
8
+ import { getStorageService } from "../core/storageService.js";
9
+
10
+ let logger: any; // Will be initialized when needed
11
+
12
+ function ensureLogger() {
13
+ if (!logger) {
14
+ logger = createLogger("QueueService");
15
+ }
16
+ return logger;
17
+ }
18
+
19
+ export interface QueueJob {
20
+ id: string;
21
+ type: string;
22
+ data: any;
23
+ status: "pending" | "processing" | "completed" | "failed";
24
+ priority: number;
25
+ retries: number;
26
+ maxRetries: number;
27
+ createdAt: Date;
28
+ updatedAt: Date;
29
+ processedAt?: Date;
30
+ error?: string;
31
+ }
32
+
33
+ export interface QueueConfig {
34
+ maxConcurrentJobs?: number;
35
+ pollingInterval?: number;
36
+ maxRetries?: number;
37
+ enablePersistence?: boolean;
38
+ }
39
+
40
+ export type JobHandler = (job: QueueJob) => Promise<void>;
41
+
42
+ export class QueueService extends EventEmitter {
43
+ private jobs: Map<string, QueueJob>;
44
+ private handlers: Map<string, JobHandler>;
45
+ private isRunning: boolean;
46
+ private activeJobs: number;
47
+ private config: Required<QueueConfig>;
48
+ private pollingTimer?: NodeJS.Timeout;
49
+ private fileHandler = getStorageService();
50
+
51
+ constructor(config: QueueConfig = {}) {
52
+ super();
53
+
54
+ this.config = {
55
+ maxConcurrentJobs: config.maxConcurrentJobs || 3,
56
+ pollingInterval: config.pollingInterval || 5000,
57
+ maxRetries: config.maxRetries || 3,
58
+ enablePersistence: config.enablePersistence || false,
59
+ };
60
+
61
+ this.jobs = new Map();
62
+ this.handlers = new Map();
63
+ this.isRunning = false;
64
+ this.activeJobs = 0;
65
+
66
+ ensureLogger().debug("QueueService initialized", this.config);
67
+ }
68
+
69
+ /**
70
+ * Register a job handler
71
+ */
72
+ registerHandler(jobType: string, handler: JobHandler): void {
73
+ this.handlers.set(jobType, handler);
74
+ ensureLogger().debug(`Registered handler for job type: ${jobType}`);
75
+ }
76
+
77
+ /**
78
+ * Add a job to the queue
79
+ */
80
+ async addJob(type: string, data: any, priority: number = 0): Promise<string> {
81
+ const jobId = `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
82
+
83
+ const job: QueueJob = {
84
+ id: jobId,
85
+ type,
86
+ data,
87
+ status: "pending",
88
+ priority,
89
+ retries: 0,
90
+ maxRetries: this.config.maxRetries,
91
+ createdAt: new Date(),
92
+ updatedAt: new Date(),
93
+ };
94
+
95
+ this.jobs.set(jobId, job);
96
+
97
+ if (this.config.enablePersistence) {
98
+ await this.persistJob(job);
99
+ }
100
+
101
+ this.emit("job:added", job);
102
+ ensureLogger().info(`Added job ${jobId} of type ${type}`);
103
+
104
+ // Check if this is a high-priority job that should preempt current processing
105
+ if (this.isRunning && priority > 5) {
106
+ // Check if we have capacity and if new job has higher priority than any processing job
107
+ const processingJobs = Array.from(this.jobs.values()).filter(
108
+ (j) => j.status === "processing",
109
+ );
110
+
111
+ if (this.activeJobs < this.config.maxConcurrentJobs) {
112
+ // We have free workers, process immediately
113
+ this.processNextJob();
114
+ } else if (processingJobs.some((j) => j.priority < priority)) {
115
+ // New job has higher priority than some processing jobs
116
+ ensureLogger().info(
117
+ `High priority job ${jobId} added, triggering immediate processing`,
118
+ );
119
+ this.processNextJob();
120
+ }
121
+ } else if (this.isRunning) {
122
+ // Normal priority job, process normally
123
+ this.processNextJob();
124
+ }
125
+
126
+ return jobId;
127
+ }
128
+
129
+ /**
130
+ * Start processing jobs
131
+ */
132
+ async start(): Promise<void> {
133
+ if (this.isRunning) {
134
+ ensureLogger().warn("Queue is already running");
135
+ return;
136
+ }
137
+
138
+ this.isRunning = true;
139
+ ensureLogger().debug("Starting queue processing");
140
+
141
+ if (this.config.enablePersistence) {
142
+ await this.loadPersistedJobs();
143
+ }
144
+
145
+ this.startPolling();
146
+ this.emit("queue:started");
147
+ }
148
+
149
+ /**
150
+ * Stop processing jobs
151
+ */
152
+ async stop(): Promise<void> {
153
+ if (!this.isRunning) {
154
+ ensureLogger().warn("Queue is not running");
155
+ return;
156
+ }
157
+
158
+ this.isRunning = false;
159
+
160
+ if (this.pollingTimer) {
161
+ clearInterval(this.pollingTimer);
162
+ this.pollingTimer = undefined;
163
+ }
164
+
165
+ ensureLogger().info("Stopping queue processing");
166
+
167
+ // Wait for active jobs to complete
168
+ while (this.activeJobs > 0) {
169
+ await new Promise((resolve) => setTimeout(resolve, 100));
170
+ }
171
+
172
+ this.emit("queue:stopped");
173
+ ensureLogger().info("Queue processing stopped");
174
+ }
175
+
176
+ /**
177
+ * Process the next job in the queue
178
+ */
179
+ private async processNextJob(): Promise<void> {
180
+ if (!this.isRunning) return;
181
+
182
+ if (this.activeJobs >= this.config.maxConcurrentJobs) {
183
+ ensureLogger().debug(
184
+ `Max concurrent jobs reached (${this.activeJobs}/${this.config.maxConcurrentJobs})`,
185
+ );
186
+ return;
187
+ }
188
+
189
+ const job = this.getNextPendingJob();
190
+ if (!job) {
191
+ ensureLogger().debug("No pending jobs found");
192
+ return;
193
+ }
194
+
195
+ const handler = this.handlers.get(job.type);
196
+ if (!handler) {
197
+ ensureLogger().error(`No handler registered for job type: ${job.type}`);
198
+ await this.failJob(job, "No handler registered");
199
+ return;
200
+ }
201
+
202
+ this.activeJobs++;
203
+ job.status = "processing";
204
+ job.processedAt = new Date();
205
+ job.updatedAt = new Date();
206
+
207
+ ensureLogger().info(
208
+ `Processing job ${job.id} (${job.type}), Active: ${this.activeJobs}`,
209
+ );
210
+ this.emit("job:started", job);
211
+
212
+ try {
213
+ await handler(job);
214
+ await this.completeJob(job);
215
+ } catch (_error: any) {
216
+ await this.handleJobError(job, _error);
217
+ } finally {
218
+ this.activeJobs--;
219
+
220
+ // Process next job immediately if available
221
+ if (this.isRunning) {
222
+ setImmediate(() => this.processNextJob());
223
+ }
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Get the next pending job based on priority
229
+ */
230
+ private getNextPendingJob(): QueueJob | null {
231
+ const pendingJobs = Array.from(this.jobs.values())
232
+ .filter((job) => job.status === "pending")
233
+ .sort((a, b) => {
234
+ // Higher priority first
235
+ if (a.priority !== b.priority) {
236
+ return b.priority - a.priority;
237
+ }
238
+ // Earlier jobs first
239
+ return a.createdAt.getTime() - b.createdAt.getTime();
240
+ });
241
+
242
+ return pendingJobs[0] || null;
243
+ }
244
+
245
+ /**
246
+ * Mark a job as completed
247
+ */
248
+ private async completeJob(job: QueueJob): Promise<void> {
249
+ job.status = "completed";
250
+ job.updatedAt = new Date();
251
+
252
+ ensureLogger().info(`Job ${job.id} completed successfully`);
253
+ this.emit("job:completed", job);
254
+
255
+ if (this.config.enablePersistence) {
256
+ await this.persistJob(job);
257
+ }
258
+
259
+ // Remove completed jobs after some time to prevent memory leak
260
+ setTimeout(() => {
261
+ this.jobs.delete(job.id);
262
+ }, 60000); // Keep for 1 minute
263
+ }
264
+
265
+ /**
266
+ * Handle job error
267
+ */
268
+ private async handleJobError(job: QueueJob, error: Error): Promise<void> {
269
+ job.retries++;
270
+ job.error = error.message;
271
+ job.updatedAt = new Date();
272
+
273
+ ensureLogger().error(`Job ${job.id} failed: ${error.message}`, error);
274
+
275
+ if (job.retries < job.maxRetries) {
276
+ job.status = "pending"; // Retry
277
+ ensureLogger().info(
278
+ `Job ${job.id} will be retried (${job.retries}/${job.maxRetries})`,
279
+ );
280
+ this.emit("job:retry", job);
281
+ } else {
282
+ await this.failJob(job, error.message);
283
+ }
284
+
285
+ if (this.config.enablePersistence) {
286
+ await this.persistJob(job);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Mark a job as failed
292
+ */
293
+ private async failJob(job: QueueJob, reason: string): Promise<void> {
294
+ job.status = "failed";
295
+ job.error = reason;
296
+ job.updatedAt = new Date();
297
+
298
+ ensureLogger().error(`Job ${job.id} failed permanently: ${reason}`);
299
+ this.emit("job:failed", job);
300
+
301
+ if (this.config.enablePersistence) {
302
+ // Move failed job to dead-letter queue directory
303
+ await this.moveToDeadLetterQueue(job);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Move failed job to dead-letter queue
309
+ */
310
+ private async moveToDeadLetterQueue(job: QueueJob): Promise<void> {
311
+ try {
312
+ // Save to data directory with failed_ prefix
313
+ await this.fileHandler.saveFile(
314
+ `failed_${job.id}_${Date.now()}.json`,
315
+ JSON.stringify(job, null, 2),
316
+ "data",
317
+ { overwrite: true },
318
+ );
319
+
320
+ // Remove original queue file from main queue directory
321
+ await this.fileHandler.deleteFile(`queue_${job.id}.json`, "data");
322
+
323
+ ensureLogger().info(`Moved failed job ${job.id} to dead-letter queue`);
324
+ } catch (_error) {
325
+ ensureLogger().error("Failed to move job to dead-letter queue:", _error);
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Start polling for jobs
331
+ */
332
+ private startPolling(): void {
333
+ this.pollingTimer = setInterval(() => {
334
+ this.processNextJob();
335
+ }, this.config.pollingInterval);
336
+
337
+ // Process immediately
338
+ this.processNextJob();
339
+ }
340
+
341
+ /**
342
+ * Persist job to file
343
+ */
344
+ private async persistJob(job: QueueJob): Promise<void> {
345
+ try {
346
+ await this.fileHandler.saveFile(
347
+ `queue_${job.id}.json`,
348
+ JSON.stringify(job, null, 2),
349
+ "data",
350
+ { overwrite: true },
351
+ );
352
+ } catch (_error) {
353
+ ensureLogger().error("Failed to persist job", _error);
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Load persisted jobs
359
+ */
360
+ private async loadPersistedJobs(): Promise<void> {
361
+ try {
362
+ const files = await this.fileHandler.listFiles(
363
+ "data",
364
+ /^queue_.*\.json$/,
365
+ );
366
+
367
+ for (const file of files) {
368
+ try {
369
+ const content = await this.fileHandler.readFile(file.name, "data");
370
+ const job = JSON.parse(content.toString());
371
+
372
+ // Convert date strings back to Date objects
373
+ job.createdAt = new Date(job.createdAt);
374
+ job.updatedAt = new Date(job.updatedAt);
375
+ if (job.processedAt) {
376
+ job.processedAt = new Date(job.processedAt);
377
+ }
378
+
379
+ // Reset processing jobs to pending
380
+ if (job.status === "processing") {
381
+ job.status = "pending";
382
+ }
383
+
384
+ this.jobs.set(job.id, job);
385
+ } catch (_error) {
386
+ ensureLogger().error(`Failed to load job from ${file.name}`, _error);
387
+ }
388
+ }
389
+
390
+ ensureLogger().debug(`Loaded ${this.jobs.size} persisted jobs`);
391
+ } catch (_error) {
392
+ ensureLogger().error("Failed to load persisted jobs", _error);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Get queue statistics
398
+ */
399
+ getStats(): {
400
+ total: number;
401
+ pending: number;
402
+ processing: number;
403
+ completed: number;
404
+ failed: number;
405
+ activeJobs: number;
406
+ } {
407
+ const jobs = Array.from(this.jobs.values());
408
+
409
+ return {
410
+ total: jobs.length,
411
+ pending: jobs.filter((j) => j.status === "pending").length,
412
+ processing: jobs.filter((j) => j.status === "processing").length,
413
+ completed: jobs.filter((j) => j.status === "completed").length,
414
+ failed: jobs.filter((j) => j.status === "failed").length,
415
+ activeJobs: this.activeJobs,
416
+ };
417
+ }
418
+
419
+ /**
420
+ * Get job by ID
421
+ */
422
+ getJob(jobId: string): QueueJob | undefined {
423
+ return this.jobs.get(jobId);
424
+ }
425
+
426
+ /**
427
+ * Get all jobs
428
+ */
429
+ getAllJobs(): QueueJob[] {
430
+ return Array.from(this.jobs.values());
431
+ }
432
+
433
+ /**
434
+ * Clear completed and failed jobs
435
+ */
436
+ clearFinishedJobs(): void {
437
+ for (const [id, job] of this.jobs) {
438
+ if (job.status === "completed" || job.status === "failed") {
439
+ this.jobs.delete(id);
440
+ }
441
+ }
442
+ ensureLogger().info("Cleared finished jobs");
443
+ }
444
+ }
445
+
446
+ export default QueueService;