archicore 0.2.8 → 0.2.9
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/cli/commands/analyzers.js +8 -1
- package/dist/cli/commands/projects.js +25 -1
- package/dist/cli/utils/session.js +29 -12
- package/dist/cli/utils/upload-utils.js +45 -41
- package/dist/code-index/ast-parser.js +169 -1
- package/dist/server/index.js +4 -3
- package/dist/server/routes/api.js +248 -2
- package/dist/server/routes/auth.js +2 -4
- package/dist/server/routes/github.js +0 -17
- package/dist/server/services/project-service.d.ts +3 -1
- package/dist/server/services/project-service.js +17 -6
- package/dist/server/services/task-queue.d.ts +98 -0
- package/dist/server/services/task-queue.js +240 -0
- package/dist/types/user.d.ts +0 -1
- package/dist/types/user.js +0 -5
- package/dist/utils/file-utils.js +122 -36
- package/package.json +1 -1
|
@@ -10,10 +10,61 @@ import { Logger } from '../../utils/logger.js';
|
|
|
10
10
|
import { ExportManager } from '../../export/index.js';
|
|
11
11
|
import { authMiddleware } from './auth.js';
|
|
12
12
|
import { FileUtils } from '../../utils/file-utils.js';
|
|
13
|
+
import { taskQueue } from '../services/task-queue.js';
|
|
13
14
|
export const apiRouter = Router();
|
|
14
15
|
// Singleton сервиса проектов
|
|
15
16
|
const projectService = new ProjectService();
|
|
16
17
|
const authService = AuthService.getInstance();
|
|
18
|
+
// Регистрация исполнителей задач
|
|
19
|
+
taskQueue.registerExecutor('full-analysis', async (task, updateProgress) => {
|
|
20
|
+
try {
|
|
21
|
+
updateProgress({ phase: 'analyzing', current: 10, message: 'Starting full analysis...' });
|
|
22
|
+
const result = await projectService.runFullAnalysis(task.projectId, (phase, progress) => {
|
|
23
|
+
updateProgress({
|
|
24
|
+
phase,
|
|
25
|
+
current: Math.min(10 + progress * 0.9, 99),
|
|
26
|
+
message: `Analyzing: ${phase}...`,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
return { success: true, data: result };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: error instanceof Error ? error.message : String(error),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
taskQueue.registerExecutor('dead-code', async (task, updateProgress) => {
|
|
39
|
+
try {
|
|
40
|
+
updateProgress({ phase: 'analyzing', current: 20, message: 'Analyzing dead code...' });
|
|
41
|
+
const result = await projectService.findDeadCode(task.projectId);
|
|
42
|
+
return { success: true, data: result };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
taskQueue.registerExecutor('security', async (task, updateProgress) => {
|
|
49
|
+
try {
|
|
50
|
+
updateProgress({ phase: 'analyzing', current: 20, message: 'Analyzing security...' });
|
|
51
|
+
const result = await projectService.analyzeSecurity(task.projectId);
|
|
52
|
+
return { success: true, data: result };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
taskQueue.registerExecutor('metrics', async (task, updateProgress) => {
|
|
59
|
+
try {
|
|
60
|
+
updateProgress({ phase: 'analyzing', current: 20, message: 'Calculating metrics...' });
|
|
61
|
+
const result = await projectService.getMetrics(task.projectId);
|
|
62
|
+
return { success: true, data: result };
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
66
|
+
}
|
|
67
|
+
});
|
|
17
68
|
// Helper to get user ID from request
|
|
18
69
|
const getUserId = (req) => {
|
|
19
70
|
return req.user?.id;
|
|
@@ -631,11 +682,17 @@ apiRouter.post('/projects/:id/export', authMiddleware, checkProjectAccess, async
|
|
|
631
682
|
/**
|
|
632
683
|
* POST /api/projects/:id/full-analysis
|
|
633
684
|
* Полный анализ проекта (все анализаторы)
|
|
685
|
+
* Запускает асинхронную задачу и возвращает task ID
|
|
686
|
+
*
|
|
687
|
+
* Query params:
|
|
688
|
+
* - async=true: вернуть task ID для отслеживания (по умолчанию)
|
|
689
|
+
* - async=false: синхронное выполнение (для обратной совместимости)
|
|
634
690
|
*/
|
|
635
691
|
apiRouter.post('/projects/:id/full-analysis', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
636
692
|
try {
|
|
637
693
|
const { id } = req.params;
|
|
638
694
|
const userId = getUserId(req);
|
|
695
|
+
const asyncMode = req.query.async !== 'false'; // По умолчанию async=true
|
|
639
696
|
// Check full analysis limit
|
|
640
697
|
if (userId) {
|
|
641
698
|
const usageResult = await authService.checkAndUpdateUsage(userId, 'analysis');
|
|
@@ -648,8 +705,20 @@ apiRouter.post('/projects/:id/full-analysis', authMiddleware, checkProjectAccess
|
|
|
648
705
|
return;
|
|
649
706
|
}
|
|
650
707
|
}
|
|
651
|
-
|
|
652
|
-
|
|
708
|
+
if (asyncMode) {
|
|
709
|
+
// Асинхронный режим - создаём задачу и возвращаем ID
|
|
710
|
+
const task = taskQueue.createTask('full-analysis', id, userId || 'anonymous');
|
|
711
|
+
res.json({
|
|
712
|
+
taskId: task.id,
|
|
713
|
+
status: task.status,
|
|
714
|
+
message: 'Analysis task queued. Poll /api/tasks/:taskId for status.',
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
// Синхронный режим - ждём завершения (для обратной совместимости)
|
|
719
|
+
const result = await projectService.runFullAnalysis(id);
|
|
720
|
+
res.json(result);
|
|
721
|
+
}
|
|
653
722
|
}
|
|
654
723
|
catch (error) {
|
|
655
724
|
Logger.error('Failed to run full analysis:', error);
|
|
@@ -789,6 +858,183 @@ apiRouter.get('/projects/:id/scan', authMiddleware, checkProjectAccess, async (r
|
|
|
789
858
|
res.status(500).json({ error: 'Failed to scan project' });
|
|
790
859
|
}
|
|
791
860
|
});
|
|
861
|
+
// ==================== TASK QUEUE ENDPOINTS ====================
|
|
862
|
+
/**
|
|
863
|
+
* GET /api/tasks/:taskId
|
|
864
|
+
* Получить статус и результат задачи
|
|
865
|
+
*/
|
|
866
|
+
apiRouter.get('/tasks/:taskId', authMiddleware, async (req, res) => {
|
|
867
|
+
try {
|
|
868
|
+
const { taskId } = req.params;
|
|
869
|
+
const userId = getUserId(req);
|
|
870
|
+
const task = taskQueue.getTask(taskId);
|
|
871
|
+
if (!task) {
|
|
872
|
+
res.status(404).json({ error: 'Task not found' });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
// Проверяем, что задача принадлежит пользователю (или админ)
|
|
876
|
+
if (task.userId !== userId && !(req.user?.tier === 'enterprise' || req.user?.id?.startsWith('admin-'))) {
|
|
877
|
+
res.status(403).json({ error: 'Access denied' });
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
res.json({
|
|
881
|
+
id: task.id,
|
|
882
|
+
type: task.type,
|
|
883
|
+
projectId: task.projectId,
|
|
884
|
+
status: task.status,
|
|
885
|
+
progress: task.progress,
|
|
886
|
+
result: task.result,
|
|
887
|
+
error: task.error,
|
|
888
|
+
createdAt: task.createdAt,
|
|
889
|
+
startedAt: task.startedAt,
|
|
890
|
+
completedAt: task.completedAt,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
catch (error) {
|
|
894
|
+
Logger.error('Failed to get task:', error);
|
|
895
|
+
res.status(500).json({ error: 'Failed to get task' });
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
/**
|
|
899
|
+
* GET /api/tasks
|
|
900
|
+
* Получить список задач пользователя
|
|
901
|
+
*/
|
|
902
|
+
apiRouter.get('/tasks', authMiddleware, async (req, res) => {
|
|
903
|
+
try {
|
|
904
|
+
const userId = getUserId(req);
|
|
905
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
906
|
+
if (!userId) {
|
|
907
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const tasks = taskQueue.getUserTasks(userId, limit);
|
|
911
|
+
res.json({ tasks });
|
|
912
|
+
}
|
|
913
|
+
catch (error) {
|
|
914
|
+
Logger.error('Failed to get tasks:', error);
|
|
915
|
+
res.status(500).json({ error: 'Failed to get tasks' });
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
/**
|
|
919
|
+
* GET /api/projects/:id/tasks
|
|
920
|
+
* Получить список задач проекта
|
|
921
|
+
*/
|
|
922
|
+
apiRouter.get('/projects/:id/tasks', authMiddleware, checkProjectAccess, async (req, res) => {
|
|
923
|
+
try {
|
|
924
|
+
const { id } = req.params;
|
|
925
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
926
|
+
const tasks = taskQueue.getProjectTasks(id, limit);
|
|
927
|
+
res.json({ tasks });
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
Logger.error('Failed to get project tasks:', error);
|
|
931
|
+
res.status(500).json({ error: 'Failed to get project tasks' });
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
/**
|
|
935
|
+
* GET /api/tasks/:taskId/progress
|
|
936
|
+
* SSE endpoint для real-time прогресса задачи
|
|
937
|
+
*/
|
|
938
|
+
apiRouter.get('/tasks/:taskId/progress', async (req, res) => {
|
|
939
|
+
const { taskId } = req.params;
|
|
940
|
+
// Handle auth via query param (EventSource doesn't support headers)
|
|
941
|
+
const token = req.query.token || req.headers.authorization?.substring(7);
|
|
942
|
+
if (!token) {
|
|
943
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const user = await authService.validateToken(token);
|
|
947
|
+
if (!user) {
|
|
948
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const task = taskQueue.getTask(taskId);
|
|
952
|
+
if (!task) {
|
|
953
|
+
res.status(404).json({ error: 'Task not found' });
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (task.userId !== user.id && !(user.tier === 'enterprise' || user.id?.startsWith('admin-'))) {
|
|
957
|
+
res.status(403).json({ error: 'Access denied' });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
// Set SSE headers
|
|
961
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
962
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
963
|
+
res.setHeader('Connection', 'keep-alive');
|
|
964
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
965
|
+
res.flushHeaders();
|
|
966
|
+
// Send current status
|
|
967
|
+
res.write(`data: ${JSON.stringify({ type: 'status', task: { id: task.id, status: task.status, progress: task.progress } })}\n\n`);
|
|
968
|
+
// If already completed, close connection
|
|
969
|
+
if (task.status === 'completed' || task.status === 'failed') {
|
|
970
|
+
res.write(`data: ${JSON.stringify({ type: 'done', result: task.result, error: task.error })}\n\n`);
|
|
971
|
+
res.end();
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
// Subscribe to task events
|
|
975
|
+
const onProgress = (updatedTask) => {
|
|
976
|
+
if (updatedTask.id === taskId) {
|
|
977
|
+
try {
|
|
978
|
+
res.write(`data: ${JSON.stringify({ type: 'progress', progress: updatedTask.progress })}\n\n`);
|
|
979
|
+
}
|
|
980
|
+
catch {
|
|
981
|
+
// Client disconnected
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
const onComplete = (updatedTask) => {
|
|
986
|
+
if (updatedTask.id === taskId) {
|
|
987
|
+
try {
|
|
988
|
+
res.write(`data: ${JSON.stringify({ type: 'done', result: updatedTask.result })}\n\n`);
|
|
989
|
+
res.end();
|
|
990
|
+
}
|
|
991
|
+
catch {
|
|
992
|
+
// Client disconnected
|
|
993
|
+
}
|
|
994
|
+
cleanup();
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
const onFailed = (updatedTask) => {
|
|
998
|
+
if (updatedTask.id === taskId) {
|
|
999
|
+
try {
|
|
1000
|
+
res.write(`data: ${JSON.stringify({ type: 'error', error: updatedTask.error })}\n\n`);
|
|
1001
|
+
res.end();
|
|
1002
|
+
}
|
|
1003
|
+
catch {
|
|
1004
|
+
// Client disconnected
|
|
1005
|
+
}
|
|
1006
|
+
cleanup();
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
const cleanup = () => {
|
|
1010
|
+
taskQueue.off('taskProgress', onProgress);
|
|
1011
|
+
taskQueue.off('taskCompleted', onComplete);
|
|
1012
|
+
taskQueue.off('taskFailed', onFailed);
|
|
1013
|
+
};
|
|
1014
|
+
taskQueue.on('taskProgress', onProgress);
|
|
1015
|
+
taskQueue.on('taskCompleted', onComplete);
|
|
1016
|
+
taskQueue.on('taskFailed', onFailed);
|
|
1017
|
+
// Handle client disconnect
|
|
1018
|
+
req.on('close', cleanup);
|
|
1019
|
+
});
|
|
1020
|
+
/**
|
|
1021
|
+
* GET /api/queue/stats
|
|
1022
|
+
* Статистика очереди задач (для админов)
|
|
1023
|
+
*/
|
|
1024
|
+
apiRouter.get('/queue/stats', authMiddleware, async (req, res) => {
|
|
1025
|
+
try {
|
|
1026
|
+
if (!(req.user?.tier === 'enterprise' || req.user?.id?.startsWith('admin-'))) {
|
|
1027
|
+
res.status(403).json({ error: 'Admin access required' });
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const stats = taskQueue.getStats();
|
|
1031
|
+
res.json(stats);
|
|
1032
|
+
}
|
|
1033
|
+
catch (error) {
|
|
1034
|
+
Logger.error('Failed to get queue stats:', error);
|
|
1035
|
+
res.status(500).json({ error: 'Failed to get queue stats' });
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
792
1038
|
// Helper function to format time
|
|
793
1039
|
function formatTime(seconds) {
|
|
794
1040
|
if (seconds < 60) {
|
|
@@ -161,8 +161,7 @@ authRouter.get('/me', authMiddleware, async (req, res) => {
|
|
|
161
161
|
limits: {
|
|
162
162
|
requestsPerDay: limits.requestsPerDay,
|
|
163
163
|
fullAnalysisPerDay: limits.fullAnalysisPerDay,
|
|
164
|
-
projectsPerDay: limits.projectsPerDay
|
|
165
|
-
maxProjectSizeMB: limits.maxProjectSizeMB
|
|
164
|
+
projectsPerDay: limits.projectsPerDay
|
|
166
165
|
},
|
|
167
166
|
usage: user.usage
|
|
168
167
|
});
|
|
@@ -297,8 +296,7 @@ authRouter.get('/usage', authMiddleware, async (req, res) => {
|
|
|
297
296
|
limits: {
|
|
298
297
|
requestsPerDay: limits.requestsPerDay,
|
|
299
298
|
fullAnalysisPerDay: limits.fullAnalysisPerDay,
|
|
300
|
-
projectsPerDay: limits.projectsPerDay
|
|
301
|
-
maxProjectSizeMB: limits.maxProjectSizeMB
|
|
299
|
+
projectsPerDay: limits.projectsPerDay
|
|
302
300
|
},
|
|
303
301
|
subscription: user.subscription
|
|
304
302
|
});
|
|
@@ -13,7 +13,6 @@ import { ProjectService } from '../services/project-service.js';
|
|
|
13
13
|
import { AuthService } from '../services/auth-service.js';
|
|
14
14
|
import { authMiddleware } from './auth.js';
|
|
15
15
|
import { Logger } from '../../utils/logger.js';
|
|
16
|
-
import { TIER_LIMITS } from '../../types/user.js';
|
|
17
16
|
export const githubRouter = Router();
|
|
18
17
|
// Services
|
|
19
18
|
const githubService = new GitHubService();
|
|
@@ -275,22 +274,6 @@ githubRouter.post('/repositories/connect', authMiddleware, async (req, res) => {
|
|
|
275
274
|
Logger.progress(`Downloading repository: ${connectedRepo.fullName} (branch: ${targetBranch})`);
|
|
276
275
|
const zipBuffer = await githubService.downloadRepository(req.user.id, connectedRepo.fullName, targetBranch);
|
|
277
276
|
Logger.info(`Downloaded ZIP: ${zipBuffer.length} bytes`);
|
|
278
|
-
// Check project size limit
|
|
279
|
-
const user = await authService.getUser(req.user.id);
|
|
280
|
-
if (user) {
|
|
281
|
-
const limits = TIER_LIMITS[user.tier];
|
|
282
|
-
const projectSizeMB = zipBuffer.length / (1024 * 1024);
|
|
283
|
-
if (projectSizeMB > limits.maxProjectSizeMB) {
|
|
284
|
-
res.status(413).json({
|
|
285
|
-
error: 'Project size limit exceeded',
|
|
286
|
-
message: `Project size (${projectSizeMB.toFixed(1)}MB) exceeds your plan limit (${limits.maxProjectSizeMB}MB). Upgrade to a higher tier for larger projects.`,
|
|
287
|
-
size: projectSizeMB,
|
|
288
|
-
limit: limits.maxProjectSizeMB,
|
|
289
|
-
tier: user.tier
|
|
290
|
-
});
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
277
|
// Create projects directory
|
|
295
278
|
const projectsDir = process.env.PROJECTS_DIR || join('.archicore', 'projects');
|
|
296
279
|
await mkdir(projectsDir, { recursive: true });
|
|
@@ -157,8 +157,10 @@ export declare class ProjectService {
|
|
|
157
157
|
getExportData(projectId: string): Promise<ExportData>;
|
|
158
158
|
/**
|
|
159
159
|
* Полный анализ проекта
|
|
160
|
+
* @param projectId - ID проекта
|
|
161
|
+
* @param onProgress - опциональный callback для отслеживания прогресса (phase, progress 0-100)
|
|
160
162
|
*/
|
|
161
|
-
runFullAnalysis(projectId: string): Promise<{
|
|
163
|
+
runFullAnalysis(projectId: string, onProgress?: (phase: string, progress: number) => void): Promise<{
|
|
162
164
|
metrics: ProjectMetrics;
|
|
163
165
|
rules: RulesCheckResult;
|
|
164
166
|
deadCode: DeadCodeResult;
|
|
@@ -788,21 +788,32 @@ export class ProjectService {
|
|
|
788
788
|
}
|
|
789
789
|
/**
|
|
790
790
|
* Полный анализ проекта
|
|
791
|
+
* @param projectId - ID проекта
|
|
792
|
+
* @param onProgress - опциональный callback для отслеживания прогресса (phase, progress 0-100)
|
|
791
793
|
*/
|
|
792
|
-
async runFullAnalysis(projectId) {
|
|
794
|
+
async runFullAnalysis(projectId, onProgress) {
|
|
793
795
|
Logger.progress('Running full analysis...');
|
|
796
|
+
onProgress?.('starting', 0);
|
|
797
|
+
// Запускаем все анализы параллельно с отслеживанием прогресса
|
|
798
|
+
const metricsPromise = this.getMetrics(projectId).then(r => { onProgress?.('metrics', 20); return r; });
|
|
799
|
+
const rulesPromise = this.checkRules(projectId).then(r => { onProgress?.('rules', 35); return r; });
|
|
800
|
+
const deadCodePromise = this.findDeadCode(projectId).then(r => { onProgress?.('dead-code', 50); return r; });
|
|
801
|
+
const duplicationPromise = this.findDuplication(projectId).then(r => { onProgress?.('duplication', 60); return r; });
|
|
802
|
+
const securityPromise = this.analyzeSecurity(projectId).then(r => { onProgress?.('security', 70); return r; });
|
|
794
803
|
const [metrics, rules, deadCode, duplication, security] = await Promise.all([
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
804
|
+
metricsPromise,
|
|
805
|
+
rulesPromise,
|
|
806
|
+
deadCodePromise,
|
|
807
|
+
duplicationPromise,
|
|
808
|
+
securityPromise,
|
|
800
809
|
]);
|
|
801
810
|
// Refactoring зависит от предыдущих результатов
|
|
811
|
+
onProgress?.('refactoring', 75);
|
|
802
812
|
const data = await this.getProjectData(projectId);
|
|
803
813
|
const fileContents = await this.getFileContents(projectId);
|
|
804
814
|
const engine = new RefactoringEngine();
|
|
805
815
|
const refactoring = await engine.analyze(data.graph, data.symbols, fileContents, metrics, duplication, deadCode, rules);
|
|
816
|
+
onProgress?.('complete', 100);
|
|
806
817
|
Logger.success('Full analysis complete');
|
|
807
818
|
return {
|
|
808
819
|
metrics,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Queue Service
|
|
3
|
+
*
|
|
4
|
+
* Асинхронная очередь задач для параллельного выполнения анализа
|
|
5
|
+
* Позволяет множеству пользователей запускать анализ одновременно
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
9
|
+
export interface TaskProgress {
|
|
10
|
+
phase: string;
|
|
11
|
+
current: number;
|
|
12
|
+
total: number;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
export interface Task<T = unknown> {
|
|
16
|
+
id: string;
|
|
17
|
+
type: string;
|
|
18
|
+
projectId: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
status: TaskStatus;
|
|
21
|
+
progress: TaskProgress;
|
|
22
|
+
result?: T;
|
|
23
|
+
error?: string;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
startedAt?: number;
|
|
26
|
+
completedAt?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface TaskResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
data?: unknown;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
type TaskExecutor = (task: Task, updateProgress: (progress: Partial<TaskProgress>) => void) => Promise<TaskResult>;
|
|
34
|
+
declare class TaskQueueService extends EventEmitter {
|
|
35
|
+
private tasks;
|
|
36
|
+
private executors;
|
|
37
|
+
private runningTasks;
|
|
38
|
+
private maxConcurrentTasks;
|
|
39
|
+
private taskTimeout;
|
|
40
|
+
constructor();
|
|
41
|
+
/**
|
|
42
|
+
* Регистрация исполнителя задач
|
|
43
|
+
*/
|
|
44
|
+
registerExecutor(taskType: string, executor: TaskExecutor): void;
|
|
45
|
+
/**
|
|
46
|
+
* Создание новой задачи
|
|
47
|
+
*/
|
|
48
|
+
createTask(type: string, projectId: string, userId: string): Task;
|
|
49
|
+
/**
|
|
50
|
+
* Получение задачи по ID
|
|
51
|
+
*/
|
|
52
|
+
getTask(taskId: string): Task | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Получение задач пользователя
|
|
55
|
+
*/
|
|
56
|
+
getUserTasks(userId: string, limit?: number): Task[];
|
|
57
|
+
/**
|
|
58
|
+
* Получение задач проекта
|
|
59
|
+
*/
|
|
60
|
+
getProjectTasks(projectId: string, limit?: number): Task[];
|
|
61
|
+
/**
|
|
62
|
+
* Обработка очереди задач
|
|
63
|
+
*/
|
|
64
|
+
private processQueue;
|
|
65
|
+
/**
|
|
66
|
+
* Выполнение задачи
|
|
67
|
+
*/
|
|
68
|
+
private executeTask;
|
|
69
|
+
/**
|
|
70
|
+
* Обновление прогресса задачи
|
|
71
|
+
*/
|
|
72
|
+
private updateTaskProgress;
|
|
73
|
+
/**
|
|
74
|
+
* Завершение задачи успешно
|
|
75
|
+
*/
|
|
76
|
+
private completeTask;
|
|
77
|
+
/**
|
|
78
|
+
* Завершение задачи с ошибкой
|
|
79
|
+
*/
|
|
80
|
+
private failTask;
|
|
81
|
+
/**
|
|
82
|
+
* Очистка старых задач (старше 1 часа)
|
|
83
|
+
*/
|
|
84
|
+
private cleanupOldTasks;
|
|
85
|
+
/**
|
|
86
|
+
* Статистика очереди
|
|
87
|
+
*/
|
|
88
|
+
getStats(): {
|
|
89
|
+
total: number;
|
|
90
|
+
pending: number;
|
|
91
|
+
running: number;
|
|
92
|
+
completed: number;
|
|
93
|
+
failed: number;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export declare const taskQueue: TaskQueueService;
|
|
97
|
+
export {};
|
|
98
|
+
//# sourceMappingURL=task-queue.d.ts.map
|