@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.
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- 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;
|