solver-sdk 6.0.0 → 6.0.1

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.
@@ -1,578 +1,134 @@
1
- /**
2
- * Константы для API путей
3
- */
4
- export var ApiEndpoints;
5
- (function (ApiEndpoints) {
6
- ApiEndpoints["PROJECTS"] = "/api/v1/projects";
7
- ApiEndpoints["PROJECT"] = "/api/v1/projects/:id";
8
- ApiEndpoints["PROJECT_INDEXING_STATUS"] = "/api/v1/projects/:id/indexing_status";
9
- ApiEndpoints["PROJECT_CANCEL_INDEXING"] = "/api/v1/projects/:id/cancel_indexing";
10
- ApiEndpoints["PROJECT_CLEAR_ERROR"] = "/api/v1/projects/:id/clear_error";
11
- })(ApiEndpoints || (ApiEndpoints = {}));
12
- /**
13
- * Замещает параметры в пути API
14
- * @param endpoint Шаблон пути API
15
- * @param params Параметры для замещения
16
- * @returns Путь API с замещенными параметрами
17
- */
18
- function formatEndpoint(endpoint, params) {
19
- let result = endpoint;
20
- for (const [key, value] of Object.entries(params)) {
21
- result = result.replace(`:${key}`, value);
22
- }
23
- return result;
24
- }
25
1
  /**
26
2
  * API для работы с проектами
27
3
  */
28
4
  export class ProjectsApi {
29
- /**
30
- * Создает новый экземпляр API для работы с проектами
31
- * @param {HttpClient} httpClient HTTP клиент
32
- */
33
5
  constructor(httpClient) {
34
6
  this.httpClient = httpClient;
35
7
  }
8
+ // ===== ОСНОВНЫЕ МЕТОДЫ УПРАВЛЕНИЯ ПРОЕКТАМИ =====
36
9
  /**
37
- * Проверяет валидность идентификатора проекта
38
- * @param {string} projectId ID проекта
39
- * @throws {Error} Если ID проекта не валиден
40
- */
41
- validateProjectId(projectId) {
42
- if (projectId === undefined || projectId === null) {
43
- throw new Error('Project ID is required');
44
- }
45
- if (typeof projectId !== 'string') {
46
- throw new Error('Project ID must be a string');
47
- }
48
- if (projectId.trim() === '') {
49
- throw new Error('Project ID cannot be empty');
50
- }
51
- }
52
- /**
53
- * Получает список всех проектов с полной информацией
54
- *
55
- * @returns {Promise<Project[]>} Массив проектов с богатой информацией:
56
- * - Базовые поля: id, name, path, description
57
- * - Статус индексации: indexStatus, indexingProgress, ready
58
- * - Статистика: fileCount, languageStats
59
- * - Ошибки: indexingErrors, indexingWarnings, lastError
60
- * - Автореиндексация: needsReindexing, changeInfo, autoReindexing
61
- *
62
- * @example
63
- * ```typescript
64
- * const projects = await sdk.getAllProjects();
65
- * projects.forEach(project => {
66
- * console.log(`${project.name}: ${project.ready ? 'Ready' : 'Indexing...'}`);
67
- * if (project.ready) {
68
- * console.log(`Files: ${project.fileCount}, Languages:`, project.languageStats);
69
- * }
70
- * });
71
- * ```
72
- *
73
- * @throws {Error} Если не удалось получить проекты или сервер вернул ошибку
10
+ * Получает все проекты пользователя
74
11
  */
75
12
  async getAllProjects() {
76
- // HttpClient уже извлекает data из axios response, возвращая прямой массив Project[]
77
13
  const projects = await this.httpClient.get('/api/v1/projects');
78
14
  return projects;
79
15
  }
80
16
  /**
81
- * Псевдоним для getAllProjects() для совместимости с тестами
82
- *
83
- * @returns {Promise<Project[]>} Массив проектов пользователя
17
+ * Альтернативное имя для getAllProjects() для совместимости
84
18
  */
85
19
  async getProjects() {
86
20
  return this.getAllProjects();
87
21
  }
88
22
  /**
89
- * Находит существующий проект или создает новый
90
- * Для совместимости с интеграционными тестами
91
- *
92
- * @param {string} projectPath Путь к проекту (для совместимости)
93
- * @param {string} projectName Имя проекта
94
- * @param {string} [description] Описание проекта
95
- * @returns {Promise<Project>} Найденный или созданный проект
23
+ * Находит или создает проект с указанным путем и именем
96
24
  */
97
- async findOrCreateProject(projectPath, projectName, description) {
98
- // Сначала пытаемся найти существующий проект по имени
99
- const projects = await this.getAllProjects();
100
- const existingProject = projects.find(p => p.name === projectName);
101
- if (existingProject) {
102
- return existingProject;
25
+ async findOrCreateProject(projectPath, projectName) {
26
+ if (!projectPath?.trim() || !projectName?.trim()) {
27
+ throw new Error('Project path and name are required');
103
28
  }
104
- // Если не найден, создаем новый
105
- return this.createProject(projectName, projectPath, { description: description || `Проект из ${projectPath}` });
29
+ const response = await this.httpClient.post('/api/v1/projects/find-or-create', {
30
+ path: projectPath.replace(/\\/g, '/').replace(/\/$/, ''),
31
+ name: projectName
32
+ });
33
+ return response;
106
34
  }
107
35
  /**
108
- * Получает список готовых к работе проектов
109
- * @returns {Promise<Project[]>} Список готовых проектов
36
+ * Получает проекты, готовые к использованию
110
37
  */
111
38
  async getReadyProjects() {
112
- // HttpClient уже извлекает data из axios response, возвращая прямой массив Project[]
113
- const projects = await this.httpClient.get('/api/v1/projects/ready');
114
- return projects;
39
+ const allProjects = await this.getAllProjects();
40
+ return allProjects.filter(project => project.indexStatus === 'indexed');
115
41
  }
116
42
  /**
117
- * Получает детальную информацию о проекте по его ID
118
- *
119
- * @param {string} projectId Уникальный идентификатор проекта
120
- * @returns {Promise<Project>} Полная информация о проекте включая:
121
- * - Статус готовности (ready) - можно ли работать с проектом
122
- * - Прогресс индексации (indexingProgress) - процент завершения
123
- * - Информацию об ошибках (indexingErrors, lastError)
124
- * - Статистику файлов (fileCount, languageStats)
125
- *
126
- * @example
127
- * ```typescript
128
- * const project = await sdk.getProject("proj_123");
129
- *
130
- * if (project.ready) {
131
- * console.log(`Проект ${project.name} готов к работе`);
132
- * console.log(`Файлов: ${project.fileCount}`);
133
- * } else if (project.indexingProgress) {
134
- * console.log(`Индексация: ${project.indexingProgress}%`);
135
- * }
136
- *
137
- * if (project.indexingErrors?.length > 0) {
138
- * console.warn('Есть ошибки индексации:', project.indexingErrors);
139
- * }
140
- * ```
141
- *
142
- * @throws {Error} Если проект не найден или произошла ошибка сервера
43
+ * Получает проект по ID
143
44
  */
144
45
  async getProject(projectId) {
145
- // HttpClient уже извлекает data из axios response, возвращая прямой объект Project
46
+ this.validateProjectId(projectId);
146
47
  const project = await this.httpClient.get(`/api/v1/projects/${projectId}`);
147
48
  return project;
148
49
  }
149
50
  /**
150
- * Создает новый проект или возвращает существующий (автоматическая дедупликация)
151
- *
152
- * @param {string} name Название проекта (отображаемое имя)
153
- * @param {string} path Абсолютный путь к проекту в файловой системе
154
- * @param {any} data Дополнительные параметры:
155
- * - description?: string - Описание проекта
156
- * - любые другие поля, которые будут переданы в backend
157
- *
158
- * @returns {Promise<Project>} Созданный или найденный проект с полной информацией.
159
- * После создания проект будет в статусе "pending" и начнет индексироваться автоматически.
160
- *
161
- * @example
162
- * ```typescript
163
- * // Простое создание
164
- * const project = await sdk.createProject(
165
- * "My React App",
166
- * "/Users/developer/my-react-app"
167
- * );
168
- *
169
- * // С описанием
170
- * const project = await sdk.createProject(
171
- * "Backend API",
172
- * "/path/to/backend",
173
- * { description: "Node.js REST API with TypeScript" }
174
- * );
175
- *
176
- * console.log(`Проект создан: ${project.name}`);
177
- * console.log(`Статус: ${project.indexStatus}`);
178
- *
179
- * // Мониторинг индексации
180
- * if (project.indexStatus === 'pending') {
181
- * console.log('Индексация начнется автоматически...');
182
- * }
183
- * ```
184
- *
185
- * @throws {Error} Если путь недоступен, проект не может быть создан, или ошибка сервера
51
+ * Создает новый проект
186
52
  */
187
- async createProject(name, path, data = {}) {
188
- // 🚀 УЛУЧШЕНО: Используем новые типы для создания проекта
189
- const projectData = {
190
- name,
191
- path,
192
- description: data.description,
193
- ...data
53
+ async createProject(name, data, options) {
54
+ if (!name?.trim()) {
55
+ throw new Error('Project name is required');
56
+ }
57
+ const requestData = {
58
+ name: name.trim(),
59
+ ...data,
60
+ ...options
194
61
  };
195
- // HttpClient уже извлекает data из axios response, возвращая прямой объект Project
196
- const project = await this.httpClient.post('/api/v1/projects', projectData);
62
+ const project = await this.httpClient.post('/api/v1/projects', requestData);
197
63
  return project;
198
64
  }
65
+ /**
66
+ * Удаляет проект
67
+ */
68
+ async deleteProject(projectId) {
69
+ this.validateProjectId(projectId);
70
+ await this.httpClient.delete(`/api/v1/projects/${projectId}`);
71
+ }
72
+ // ===== ИНДЕКСАЦИЯ И СТАТУС =====
199
73
  /**
200
74
  * Получает статус проекта
201
- * @param {string} projectId ID проекта
202
- * @returns {Promise<any>} Статус проекта
203
75
  */
204
76
  async getProjectStatus(projectId) {
205
- return this.httpClient.get(`/api/v1/projects/${projectId}/status`);
77
+ this.validateProjectId(projectId);
78
+ return await this.httpClient.get(`/api/v1/projects/${projectId}/status`);
206
79
  }
207
80
  /**
208
81
  * Запускает индексацию проекта
209
- * @param {string} projectId ID проекта
210
- * @param {any} options Опции индексации
211
- * @returns {Promise<any>} Результат запуска индексации
212
82
  */
213
83
  async startIndexing(projectId, options = {}) {
214
- return this.httpClient.post(`/api/v1/projects/${projectId}/index`, options);
215
- }
216
- /**
217
- * Удаляет проект
218
- * @param projectId - ID проекта
219
- * @returns Promise<{ success: boolean }>
220
- * @throws Error если метод не поддерживается
221
- */
222
- /**
223
- * 🗑️ Удалить проект полностью (ВНИМАНИЕ: Удаляет ВСЕ данные!)
224
- *
225
- * Удаляет проект и все связанные данные из системы:
226
- * - Все чанки и векторы из Qdrant
227
- * - Все записи из PostgreSQL
228
- * - Метаданные проекта
229
- * - Сессии синхронизации
230
- *
231
- * @param projectId ID проекта для удаления
232
- * @returns Результат удаления
233
- *
234
- * @example
235
- * ```typescript
236
- * // ВНИМАНИЕ: Это действие необратимо!
237
- * const result = await sdk.projects.deleteProject(projectId);
238
- *
239
- * if (result.success) {
240
- * console.log('✅ Проект полностью удален');
241
- * }
242
- * ```
243
- */
244
- async deleteProject(projectId) {
245
84
  this.validateProjectId(projectId);
246
- try {
247
- const response = await this.httpClient.delete(`/api/v1/projects/${projectId}`);
248
- return {
249
- success: response.success || false,
250
- message: response.message || 'Project deleted successfully'
251
- };
252
- }
253
- catch (error) {
254
- console.error('Failed to delete project:', error);
255
- return {
256
- success: false,
257
- message: `Failed to delete project: ${error instanceof Error ? error.message : String(error)}`
258
- };
259
- }
85
+ const payload = options.force
86
+ ? { action: 'force_restart' }
87
+ : {};
88
+ return await this.httpClient.post(`/api/v1/projects/${projectId}/index`, payload);
260
89
  }
261
90
  /**
262
- * Получает текущий статус индексации проекта (единичный запрос)
263
- *
264
- * ⚠️ ВАЖНО: Для real-time уведомлений используйте WebSocket:
265
- * ```typescript
266
- * // Правильно (real-time):
267
- * await sdk.connectWebSocket();
268
- * sdk.projectSync.on('sync-status-update', (status) => {
269
- * console.log('Статус обновлен:', status);
270
- * });
271
- *
272
- * // Неправильно (не делайте polling):
273
- * // setInterval(() => sdk.projects.getIndexingStatus(id), 2000); // ❌
274
- * ```
275
- *
276
- * @param {string} projectId Идентификатор проекта
277
- * @returns {Promise<any>} Текущий статус индексации (snapshot)
91
+ * Получает статус индексации проекта
278
92
  */
279
93
  async getIndexingStatus(projectId) {
280
94
  this.validateProjectId(projectId);
281
- return this.httpClient.get(`/api/v1/projects/${projectId}/indexing-status`);
95
+ return await this.httpClient.get(`/api/v1/projects/${projectId}/indexing-status`);
282
96
  }
283
97
  /**
284
98
  * Отменяет индексацию проекта
285
- * @param {string} projectId Идентификатор проекта
286
- * @returns {Promise<boolean>} Результат отмены индексации
287
99
  */
288
100
  async cancelIndexing(projectId) {
289
- if (!projectId) {
290
- throw new Error('Project ID is required');
291
- }
292
- return this.httpClient.post(formatEndpoint(ApiEndpoints.PROJECT_CANCEL_INDEXING, { id: projectId }))
293
- .then(response => {
294
- return true;
295
- })
296
- .catch(error => {
297
- throw new Error(`Failed to cancel indexing: ${error.message}`);
298
- });
299
- }
300
- /**
301
- * Останавливает индексацию проекта (алиас для cancelIndexing)
302
- * @param {string} projectId Идентификатор проекта
303
- * @returns {Promise<boolean>} Результат остановки индексации
304
- */
305
- async stopIndexing(projectId) {
306
- return this.cancelIndexing(projectId);
307
- }
308
- /**
309
- * Очищает ошибку индексации проекта
310
- * @param {string} projectId Идентификатор проекта
311
- * @returns {Promise<boolean>} Результат очистки ошибки
312
- */
313
- async clearIndexingError(projectId) {
314
- this.validateProjectId(projectId);
315
- return this.httpClient.post(formatEndpoint(ApiEndpoints.PROJECT_CLEAR_ERROR, { id: projectId }))
316
- .then(() => true)
317
- .catch(error => {
318
- throw new Error(`Failed to clear indexing error: ${error.message}`);
319
- });
320
- }
321
- /**
322
- * Обновляет индекс конкретного файла в проекте
323
- * @param {string} projectId ID проекта
324
- * @param {string} filePath Путь к файлу (относительно корня проекта)
325
- * @param {Object} options Опции обновления индекса
326
- * @param {string} [options.content] Содержимое файла (если не указано, будет прочитано с диска)
327
- * @param {boolean} [options.force=false] Принудительная переиндексация, даже если файл не изменился
328
- * @param {string} [options.language] Язык файла (если не указан, будет определен автоматически)
329
- * @param {boolean} [options.updateDependencies=false] Обновлять зависимости после индексации файла
330
- * @returns {Promise<FileIndexResponse>} Информация об обновленном индексе файла
331
- */
332
- async updateFileIndex(projectId, filePath, options = {}) {
333
101
  this.validateProjectId(projectId);
334
- if (!filePath) {
335
- throw new Error('Путь к файлу не может быть пустым');
336
- }
337
- // Кодируем путь для URL, чтобы избежать проблем со специальными символами
338
- const encodedFilePath = encodeURIComponent(filePath);
339
- return this.httpClient.post('/api/v1/projects/' + projectId + '/files/' + encodedFilePath + '/index', options);
340
- }
341
- /**
342
- * Кэширует соответствие пути и ID проекта
343
- * @param path Путь к проекту
344
- * @param projectId ID проекта
345
- * @private
346
- */
347
- cacheProjectId(path, projectId) {
348
- const normalizedPath = path.replace(/\\/g, '/').replace(/\/$/, '');
349
- if (typeof localStorage !== 'undefined') {
350
- // Браузерное окружение
351
- const cachedProjects = JSON.parse(localStorage.getItem('solverSdkProjectCache') || '{}');
352
- cachedProjects[normalizedPath] = projectId;
353
- localStorage.setItem('solverSdkProjectCache', JSON.stringify(cachedProjects));
354
- }
355
- else if (typeof process !== 'undefined') {
356
- // Node.js окружение (для VS Code/Cursor)
357
- if (!global.solverSdkProjectCache) {
358
- global.solverSdkProjectCache = {};
359
- }
360
- global.solverSdkProjectCache[normalizedPath] = projectId;
361
- }
362
- else {
363
- throw new Error('Неподдерживаемая среда выполнения - нет localStorage и process');
364
- }
365
- }
366
- /**
367
- * Получает ID проекта из кэша
368
- * @param path Путь к проекту
369
- * @returns ID проекта или null, если не найдено
370
- * @private
371
- */
372
- getCachedProjectId(path) {
373
- const normalizedPath = path.replace(/\\/g, '/').replace(/\/$/, '');
374
- if (typeof localStorage !== 'undefined') {
375
- // Браузерное окружение
376
- const cachedProjects = JSON.parse(localStorage.getItem('solverSdkProjectCache') || '{}');
377
- return cachedProjects[normalizedPath] || null;
378
- }
379
- else if (typeof process !== 'undefined' && global.solverSdkProjectCache) {
380
- // Node.js окружение
381
- return global.solverSdkProjectCache[normalizedPath] || null;
382
- }
383
- return null;
384
- }
385
- /**
386
- * Получает или создает проект по пути
387
- * @param path Путь к проекту
388
- * @param name Имя проекта (опционально, если не указано - будет сгенерировано из пути)
389
- * @returns Данные проекта
390
- */
391
- async getOrCreateProject(path, name) {
392
- if (!path) {
393
- throw new Error('Путь к проекту не может быть пустым');
394
- }
395
- const normalizedPath = path.replace(/\\/g, '/').replace(/\/$/, '');
396
- // Сначала проверяем кэш
397
- const cachedProjectId = this.getCachedProjectId(normalizedPath);
398
- if (cachedProjectId) {
399
- // Если в кэше есть ID - пытаемся получить проект, при ошибке бросаем исключение
400
- const project = await this.getProject(cachedProjectId);
401
- return project;
402
- }
403
- // Вызываем API для получения/создания проекта
404
- const project = await this.httpClient.post('/api/v1/projects', {
405
- path: normalizedPath,
406
- name: name || normalizedPath.split('/').pop() || 'Unnamed Project'
407
- });
408
- // Кэшируем результат
409
- this.cacheProjectId(normalizedPath, project.id);
410
- return project;
411
- }
412
- /**
413
- * Индексирует проект по указанному пути
414
- * @param path Путь к проекту
415
- * @param options Опции индексации
416
- * @returns Результат операции индексации
417
- */
418
- async indexProjectByPath(path, options = {}) {
419
- if (!path) {
420
- throw new Error('Путь к проекту не может быть пустым');
421
- }
422
- const normalizedPath = path.replace(/\\/g, '/').replace(/\/$/, '');
423
- const projectName = options.name || normalizedPath.split('/').pop() || 'Unnamed Project';
424
- try {
425
- // ✅ ЧИСТЫЙ ПОДХОД: Используем стандартный endpoint с автоматической дедупликацией
426
- const project = await this.httpClient.post('/api/v1/projects', {
427
- path: normalizedPath,
428
- name: projectName
429
- });
430
- if (!project || !project.id) {
431
- throw new Error('Не удалось создать проект - отсутствует ID');
432
- }
433
- // ✅ СОВРЕМЕННЫЙ API: 2. Запускаем индексацию
434
- const indexingResult = await this.httpClient.post(`/api/v1/projects/${project.id}/index`, {
435
- forceFull: options.forceFull,
436
- excludePatterns: options.excludePatterns
437
- });
438
- return {
439
- success: true,
440
- status: 'indexing',
441
- projectId: project.id
442
- };
443
- }
444
- catch (error) {
445
- // Улучшаем сообщение об ошибке
446
- const errorMessage = error instanceof Error ? error.message : String(error);
447
- throw new Error(`Не удалось выполнить индексацию проекта по пути: ${errorMessage}`);
448
- }
449
- }
450
- /**
451
- * Создание и индексация проекта в одной операции
452
- * @param path Путь к проекту
453
- * @param options Опции создания и индексации
454
- * @param {string} [options.name] Имя проекта (если не указано, будет получено из пути)
455
- * @param {boolean} [options.forceFull=false] Принудительная полная индексация
456
- * @param {string} [options.indexingMode] Режим индексации: 'full', 'incremental', 'auto'
457
- * @param {string[]} [options.includePatterns] Паттерны для включения файлов
458
- * @param {string[]} [options.excludePatterns] Паттерны для исключения файлов
459
- * @returns Информация о созданном проекте и начатой индексации
460
- */
461
- async createAndIndexProject(path, options = {}) {
462
- try {
463
- // Преобразуем indexingMode в forceFull для совместимости с бэкендом
464
- let requestOptions = {
465
- name: options.name,
466
- excludePatterns: options.excludePatterns
467
- };
468
- if (options.indexingMode === 'full' || options.forceFull) {
469
- requestOptions.forceFull = true;
470
- }
471
- if (options.includePatterns) {
472
- requestOptions.includePatterns = options.includePatterns;
473
- }
474
- // Используем API для индексации по пути, который также создает проект если его нет
475
- const result = await this.indexProjectByPath(path, requestOptions);
476
- // Проверяем, что результат содержит нужные поля
477
- if (!result || !result.projectId) {
478
- throw new Error('Сервер вернул некорректный ответ без идентификатора проекта');
479
- }
480
- return {
481
- projectId: result.projectId,
482
- indexingStatus: result.status || 'indexing'
483
- };
484
- }
485
- catch (error) {
486
- const errorMessage = error instanceof Error ? error.message : String(error);
487
- throw new Error(`Не удалось создать и проиндексировать проект: ${errorMessage}`);
488
- }
489
- }
490
- /**
491
- * Проверяет существование проекта и создает резервную копию при необходимости
492
- * @param projectId ID проекта для проверки
493
- * @param options Опции для создания резервной копии
494
- * @returns Существующий проект или созданную резервную копию
495
- */
496
- async ensureProjectExists(projectId, options = {}) {
497
102
  try {
498
- this.validateProjectId(projectId);
499
- // Пытаемся получить проект по ID
500
- try {
501
- const project = await this.getProject(projectId);
502
- return project;
503
- }
504
- catch (error) {
505
- // Проверяем, нужно ли создавать резервную копию
506
- if (!options.createBackup) {
507
- // Если резервная копия не требуется, пробрасываем ошибку
508
- const errorMessage = error instanceof Error ? error.message : String(error);
509
- throw new Error(`Проект с ID ${projectId} не найден: ${errorMessage}`);
510
- }
511
- // Проверяем наличие пути для резервной копии
512
- if (!options.backupPath) {
513
- throw new Error('Для создания резервной копии необходимо указать путь (backupPath)');
514
- }
515
- const backupName = options.backupName || `Резервная копия для ${projectId}`;
516
- // Создаем новый проект как резервную копию
517
- console.log(`[ProjectsApi] Создание резервной копии проекта ${projectId} с именем "${backupName}" по пути ${options.backupPath}`);
518
- return await this.createProject(backupName, options.backupPath);
519
- }
103
+ await this.httpClient.post(`/api/v1/projects/${projectId}/cancel-indexing`);
104
+ return true;
520
105
  }
521
106
  catch (error) {
522
- const errorMessage = error instanceof Error ? error.message : String(error);
523
- throw new Error(`Ошибка при проверке/создании проекта ${projectId}: ${errorMessage}`);
107
+ console.error('Failed to cancel indexing:', error);
108
+ return false;
524
109
  }
525
110
  }
526
111
  /**
527
- * Получает или создает проект по пути с расширенными опциями
528
- * @param path Путь к проекту
529
- * @param options Дополнительные опции
530
- * @returns Найденный или созданный проект
112
+ * Очищает ошибку индексации
531
113
  */
532
- async getOrCreateProjectByPath(path, options = {}) {
533
- if (!path) {
534
- throw new Error('Путь к проекту не может быть пустым');
535
- }
536
- const normalizedPath = path.replace(/\\/g, '/').replace(/\/$/, '');
114
+ async clearIndexingError(projectId) {
115
+ this.validateProjectId(projectId);
537
116
  try {
538
- // Сначала проверяем кэш
539
- const cachedProjectId = this.getCachedProjectId(normalizedPath);
540
- if (cachedProjectId && options.preferExisting !== false) {
541
- try {
542
- const project = await this.getProject(cachedProjectId);
543
- return project;
544
- }
545
- catch (error) {
546
- // Если ошибка - продолжаем с API запросом
547
- console.warn(`[ProjectsApi] Не удалось получить проект из кэша: ${error instanceof Error ? error.message : String(error)}`);
548
- }
549
- }
550
- // Если проект не найден в кэше или не смогли его получить, создаем новый через стандартный API
551
- const project = await this.httpClient.post('/api/v1/projects', {
552
- path: normalizedPath,
553
- name: options.name || normalizedPath.split('/').pop() || 'Unnamed Project'
554
- });
555
- // Кэшируем полученный проект
556
- if (project && project.id) {
557
- this.cacheProjectId(normalizedPath, project.id);
558
- }
559
- return project;
117
+ await this.httpClient.post(`/api/v1/projects/${projectId}/clear-error`);
118
+ return true;
560
119
  }
561
120
  catch (error) {
562
- throw new Error(`Не удалось получить или создать проект: ${error.message}`);
121
+ console.error('Failed to clear indexing error:', error);
122
+ return false;
563
123
  }
564
124
  }
565
125
  // ===== DELTA CHUNKING МЕТОДЫ =====
566
126
  /**
567
127
  * Инициализация синхронизации проекта для Delta Chunking
568
- * @param projectId ID проекта
569
- * @param request Данные для инициализации синхронизации
570
- * @returns Результат инициализации с needsSync и опциональным sessionId
571
128
  */
572
129
  async sendInitialSync(projectId, request) {
573
130
  this.validateProjectId(projectId);
574
131
  const response = await this.httpClient.post(`/api/v1/projects/${projectId}/sync-init`, request);
575
- // ✅ ИСПРАВЛЕНО: Проверяем sessionId только если needsSync = true
576
132
  if (response.needsSync && !response.sessionId) {
577
133
  throw new Error('Server did not return sessionId when sync is needed');
578
134
  }
@@ -580,9 +136,6 @@ export class ProjectsApi {
580
136
  }
581
137
  /**
582
138
  * Отправка батча зашифрованных чанков для Delta Chunking
583
- * @param projectId ID проекта
584
- * @param batchRequest Батч зашифрованных чанков
585
- * @returns Результат обработки батча
586
139
  */
587
140
  async sendDeltaSync(projectId, batchRequest) {
588
141
  this.validateProjectId(projectId);
@@ -591,8 +144,6 @@ export class ProjectsApi {
591
144
  }
592
145
  /**
593
146
  * Получение статуса синхронизации Delta Chunking
594
- * @param projectId ID проекта
595
- * @returns Текущий статус синхронизации
596
147
  */
597
148
  async getDeltaSyncStatus(projectId) {
598
149
  this.validateProjectId(projectId);
@@ -613,13 +164,10 @@ export class ProjectsApi {
613
164
  }
614
165
  /**
615
166
  * Завершение синхронизации Delta Chunking
616
- * @param projectId ID проекта
617
- * @returns Результат завершения синхронизации
618
167
  */
619
168
  async finalizeDeltaSync(projectId) {
620
169
  this.validateProjectId(projectId);
621
170
  const response = await this.httpClient.post(`/api/v1/projects/${projectId}/sync-finalize`, {});
622
- // HttpClient уже извлекает data из axios response, response - это прямой объект от backend
623
171
  return {
624
172
  success: response.success || false,
625
173
  projectId,
@@ -633,88 +181,39 @@ export class ProjectsApi {
633
181
  }
634
182
  /**
635
183
  * Отмена синхронизации Delta Chunking
636
- * @param projectId ID проекта
637
- * @returns Результат отмены
638
184
  */
639
185
  async cancelDeltaSync(projectId) {
640
186
  this.validateProjectId(projectId);
641
187
  const response = await this.httpClient.delete(`/api/v1/projects/${projectId}/sync-cancel`);
642
- // HttpClient уже извлекает data из axios response, response - это прямой объект от backend
643
188
  return {
644
- success: response.success || false,
189
+ success: response.success !== false,
645
190
  message: response.message
646
191
  };
647
192
  }
193
+ // ===== PROJECT STATE И SMART SYNC =====
648
194
  /**
649
- * 🔍 Получить состояние проекта без инициализации синхронизации
650
- *
651
- * Проверяет текущее состояние проекта на сервере. Если передан clientRootHash,
652
- * дополнительно проверяет нужна ли синхронизация.
653
- *
654
- * @param projectId ID проекта
655
- * @param clientRootHash (опционально) Merkle root hash клиента для проверки sync
656
- * @returns Состояние проекта
657
- *
658
- * @example
659
- * ```typescript
660
- * // Проверка только состояния проекта
661
- * const state = await sdk.projects.getProjectState(projectId);
662
- * console.log(`Проект "${state.projectName}" содержит ${state.totalChunks} чанков`);
663
- *
664
- * // Проверка нужна ли синхронизация
665
- * const clientHash = await computeMerkleRootHash(workspacePath);
666
- * const stateWithSync = await sdk.projects.getProjectState(projectId, clientHash);
667
- *
668
- * if (stateWithSync.syncRequired) {
669
- * console.log("Требуется синхронизация");
670
- * await sdk.projects.initializeDeltaSync(projectId, clientHash);
671
- * } else {
672
- * console.log("Проект актуален, синхронизация не нужна");
673
- * }
674
- * ```
195
+ * Получение состояния проекта с проверкой синхронизации
675
196
  */
676
197
  async getProjectState(projectId, clientRootHash) {
677
198
  this.validateProjectId(projectId);
678
- const params = clientRootHash ? { clientRootHash } : {};
679
- const response = await this.httpClient.get(`/api/v1/projects/${projectId}/state`, params);
680
- // Конвертируем даты из строк в Date объекты
199
+ const url = clientRootHash
200
+ ? `/api/v1/projects/${projectId}/state?clientRootHash=${encodeURIComponent(clientRootHash)}`
201
+ : `/api/v1/projects/${projectId}/state`;
202
+ const response = await this.httpClient.get(url);
681
203
  return {
682
- projectId: response.projectId,
683
- projectName: response.projectName,
684
- merkleRootHash: response.merkleRootHash,
204
+ projectId: response.projectId || projectId,
205
+ projectName: response.projectName || 'Unknown',
206
+ merkleRootHash: response.merkleRootHash || null,
685
207
  lastIndexedAt: response.lastIndexedAt ? new Date(response.lastIndexedAt) : null,
686
208
  totalChunks: response.totalChunks || 0,
687
- indexingStatus: response.indexingStatus,
209
+ indexingStatus: response.indexingStatus || 'unknown',
688
210
  syncRequired: response.syncRequired,
689
211
  lastSyncSessionId: response.lastSyncSessionId
690
212
  };
691
213
  }
214
+ // ===== SESSION RECOVERY =====
692
215
  /**
693
- * 🔄 Проверить есть ли прерванные сессии синхронизации для проекта
694
- *
695
- * Используется при открытии проекта для показа пользователю опции
696
- * "Continue indexing" если есть незавершенная синхронизация.
697
- *
698
- * @param projectId ID проекта
699
- * @returns Статус восстановления с информацией о прерванной сессии
700
- *
701
- * @example
702
- * ```typescript
703
- * // Проверка при открытии проекта
704
- * const recoveryStatus = await sdk.projects.getRecoveryStatus(projectId);
705
- *
706
- * if (recoveryStatus.hasInterruptedSession) {
707
- * // Показать UI: "Continue indexing" button
708
- * const info = recoveryStatus.recoveryInfo!;
709
- * console.log(`Прерванная синхронизация: ${info.progress.percentage}% завершено`);
710
- *
711
- * // Пользователь нажал "Continue"
712
- * const resumed = await sdk.projects.resumeSync(projectId);
713
- * } else {
714
- * // Обычная синхронизация
715
- * await sdk.projects.initializeDeltaSync(projectId, merkleHash);
716
- * }
717
- * ```
216
+ * Получение статуса восстановления сессии
718
217
  */
719
218
  async getRecoveryStatus(projectId) {
720
219
  this.validateProjectId(projectId);
@@ -723,228 +222,117 @@ export class ProjectsApi {
723
222
  hasInterruptedSession: response.hasInterruptedSession || false,
724
223
  recoveryInfo: response.recoveryInfo ? {
725
224
  sessionId: response.recoveryInfo.sessionId,
726
- projectId: response.recoveryInfo.projectId,
727
- projectName: response.recoveryInfo.projectName,
728
- status: response.recoveryInfo.status,
729
- progress: response.recoveryInfo.progress,
730
- interruptedAt: response.recoveryInfo.interruptedAt ?
731
- new Date(response.recoveryInfo.interruptedAt) : null,
732
- canResume: response.recoveryInfo.canResume,
733
- resumeReason: response.recoveryInfo.resumeReason,
734
- estimatedTimeRemaining: response.recoveryInfo.estimatedTimeRemaining,
735
- } : undefined,
225
+ projectId: response.recoveryInfo.projectId || projectId,
226
+ projectName: response.recoveryInfo.projectName || 'Unknown',
227
+ status: response.recoveryInfo.status || 'interrupted',
228
+ progress: {
229
+ received: response.recoveryInfo.progress?.totalChunks || 0,
230
+ processed: response.recoveryInfo.progress?.processedChunks || 0,
231
+ total: response.recoveryInfo.progress?.totalChunks || 0,
232
+ percentage: response.recoveryInfo.progress?.percentage || 0
233
+ },
234
+ interruptedAt: response.recoveryInfo.lastActivity ? new Date(response.recoveryInfo.lastActivity) : new Date(),
235
+ resumeReason: response.recoveryInfo.resumeReason || 'session_recovery',
236
+ canResume: response.recoveryInfo.canResume !== false
237
+ } : undefined
736
238
  };
737
239
  }
738
240
  /**
739
- * 🔄 Возобновить прерванную сессию синхронизации
740
- *
741
- * Подготавливает прерванную сессию к возобновлению. После успешного
742
- * вызова клиент может продолжить отправку чанков через обычный endpoint.
743
- *
744
- * @param projectId ID проекта
745
- * @param options Опции возобновления (форсировать, пропустить дубликаты, etc.)
746
- * @returns Результат возобновления с информацией для продолжения
747
- *
748
- * @example
749
- * ```typescript
750
- * // Возобновить с настройками по умолчанию
751
- * const resumed = await sdk.projects.resumeSync(projectId);
752
- *
753
- * if (resumed.success) {
754
- * console.log(`Сессия ${resumed.sessionId} готова к возобновлению`);
755
- *
756
- * // Продолжить отправку чанков с определенного батча
757
- * if (resumed.continueFromBatch) {
758
- * console.log(`Начать с батча ${resumed.continueFromBatch}`);
759
- * }
760
- *
761
- * // Использовать обычный endpoint для отправки
762
- * await sdk.deltaChunking.sendBatch(projectId, chunks);
763
- * }
764
- *
765
- * // Возобновить с принудительными настройками
766
- * const forced = await sdk.projects.resumeSync(projectId, {
767
- * forceResume: true,
768
- * skipDuplicates: true,
769
- * continueFromLastBatch: true
770
- * });
771
- * ```
241
+ * Восстановление прерванной синхронизации
772
242
  */
773
243
  async resumeSync(projectId, options = {}) {
774
244
  this.validateProjectId(projectId);
775
- const response = await this.httpClient.post(`/api/v1/projects/${projectId}/resume-sync`, options);
245
+ const response = await this.httpClient.post(`/api/v1/projects/${projectId}/resume-sync`, {
246
+ skipDuplicates: options.skipDuplicates !== false,
247
+ continueFromLastBatch: options.continueFromLastBatch !== false
248
+ });
776
249
  return {
777
- success: response.success || false,
778
- message: response.message || '',
779
- sessionId: response.sessionId,
250
+ success: response.success !== false,
251
+ message: response.message,
780
252
  continueFromBatch: response.continueFromBatch,
781
- resumeEndpoint: response.resumeEndpoint,
253
+ sessionId: response.sessionId
782
254
  };
783
255
  }
784
256
  /**
785
- * 🗑️ Отменить прерванную сессию восстановления
786
- *
787
- * Отменяет прерванную сессию если пользователь решил не восстанавливать
788
- * синхронизацию. После этого будет выполнена обычная полная синхронизация.
789
- *
790
- * @param projectId ID проекта
791
- * @returns Результат отмены
792
- *
793
- * @example
794
- * ```typescript
795
- * // Пользователь нажал "Start fresh" вместо "Continue"
796
- * const cancelled = await sdk.projects.cancelRecovery(projectId);
797
- *
798
- * if (cancelled.success) {
799
- * console.log('Прерванная сессия отменена, начинаем полную синхронизацию');
800
- * await sdk.projects.initializeDeltaSync(projectId, newMerkleHash);
801
- * }
802
- * ```
257
+ * Отмена восстановления сессии
803
258
  */
804
259
  async cancelRecovery(projectId) {
805
260
  this.validateProjectId(projectId);
806
- const response = await this.httpClient.delete(`/api/v1/projects/${projectId}/cancel-recovery`);
261
+ const response = await this.httpClient.delete(`/api/v1/projects/${projectId}/recovery`);
807
262
  return {
808
- success: response.success || false,
809
- message: response.message || '',
263
+ success: response.success !== false,
264
+ message: response.message
810
265
  };
811
266
  }
812
267
  /**
813
- * 🚀 Инициализировать Delta-Chunking синхронизацию
814
- *
815
- * Начинает новую сессию синхронизации с использованием delta-chunking.
816
- * Алиас для удобства - делегирует к DeltaChunkingManager.
817
- *
818
- * @param projectId ID проекта
819
- * @param clientRootHash Merkle root hash клиента
820
- * @returns Результат инициализации
821
- *
822
- * @example
823
- * ```typescript
824
- * const merkleHash = await computeMerkleRootHash(workspacePath);
825
- * const result = await sdk.projects.initializeDeltaSync(projectId, merkleHash);
826
- *
827
- * if (result.needsSync) {
828
- * console.log(`Started sync session: ${result.sessionId}`);
829
- * // Продолжить отправку чанков...
830
- * } else {
831
- * console.log('Project is up to date');
832
- * }
833
- * ```
268
+ * Инициализация Delta Sync
834
269
  */
835
270
  async initializeDeltaSync(projectId, clientRootHash) {
836
271
  this.validateProjectId(projectId);
837
- // Делегируем к DeltaChunkingApi
838
- const deltaApi = new (await import('./delta-chunking-api.js')).DeltaChunkingApi(this.httpClient);
839
- return await deltaApi.initSync(projectId, { clientRootHash });
272
+ if (!clientRootHash?.trim()) {
273
+ throw new Error('Client root hash is required');
274
+ }
275
+ const response = await this.httpClient.post(`/api/v1/projects/${projectId}/initialize-delta-sync`, {
276
+ clientRootHash: clientRootHash.trim()
277
+ });
278
+ return {
279
+ needsSync: response.needsSync !== false,
280
+ sessionId: response.sessionId,
281
+ lastSyncHash: response.lastSyncHash
282
+ };
840
283
  }
841
284
  /**
842
- * 🔄 Сбросить индексацию проекта (очистить данные, сохранить проект)
843
- *
844
- * Очищает все проиндексированные данные, но сохраняет сам проект:
845
- * - Удаляет все векторы из Qdrant
846
- * - Удаляет все chunks из PostgreSQL
847
- * - Удаляет сессии синхронизации
848
- * - Сбрасывает статус индексации
849
- *
850
- * @param projectId ID проекта
851
- * @returns Результат сброса с статистикой
852
- *
853
- * @example
854
- * ```typescript
855
- * // Сброс индексации (проект остается)
856
- * const result = await sdk.projects.resetIndexing(projectId);
857
- *
858
- * console.log(`Cleared: ${result.clearedData.vectorsDeleted} vectors, ${result.clearedData.chunksDeleted} chunks`);
859
- *
860
- * // После сброса можно начать новую индексацию
861
- * const merkleHash = await computeMerkleRootHash(workspacePath);
862
- * await sdk.projects.initializeDeltaSync(projectId, merkleHash);
863
- * ```
285
+ * Сброс индексации проекта
864
286
  */
865
287
  async resetIndexing(projectId) {
866
288
  this.validateProjectId(projectId);
867
289
  const response = await this.httpClient.post(`/api/v1/projects/${projectId}/reset-indexing`);
868
290
  return {
869
- success: response.success || false,
870
- message: response.message || '',
871
- clearedData: response.clearedData || {
872
- vectorsDeleted: 0,
873
- chunksDeleted: 0,
874
- sessionsDeleted: 0
875
- }
291
+ success: response.success !== false,
292
+ message: response.message || 'Reset completed',
293
+ clearedChunks: response.clearedChunks,
294
+ clearedFiles: response.clearedFiles
876
295
  };
877
296
  }
878
297
  /**
879
- * 🔄 Перезапустить индексацию проекта (сброс + подготовка к новой)
880
- *
881
- * Выполняет полный перезапуск индексации:
882
- * 1. Очищает все существующие данные
883
- * 2. Сбрасывает статус проекта
884
- * 3. Подготавливает к новой синхронизации
885
- *
886
- * @param projectId ID проекта
887
- * @returns Результат перезапуска
888
- *
889
- * @example
890
- * ```typescript
891
- * // Перезапуск индексации
892
- * const result = await sdk.projects.restartIndexing(projectId);
893
- *
894
- * if (result.readyForNewSync) {
895
- * console.log('Project ready for new indexing');
896
- *
897
- * // Начать новую синхронизацию
898
- * const merkleHash = await computeMerkleRootHash(workspacePath);
899
- * await sdk.projects.initializeDeltaSync(projectId, merkleHash);
900
- * }
901
- * ```
298
+ * Перезапуск индексации проекта
902
299
  */
903
300
  async restartIndexing(projectId) {
904
301
  this.validateProjectId(projectId);
905
302
  const response = await this.httpClient.post(`/api/v1/projects/${projectId}/restart-indexing`);
906
303
  return {
907
- success: response.success || false,
908
- message: response.message || '',
909
- resetData: response.resetData || {
910
- vectorsDeleted: 0,
911
- chunksDeleted: 0,
912
- sessionsDeleted: 0
913
- },
914
- readyForNewSync: response.readyForNewSync || false
304
+ success: response.success !== false,
305
+ message: response.message || 'Restart initiated',
306
+ newSessionId: response.newSessionId
915
307
  };
916
308
  }
917
309
  /**
918
- * 🔍 Получить соответствие обфусцированных путей файлов
919
- *
920
- * Возвращает список obfuscatedPath для всех файлов проекта.
921
- * Используется для точной очистки удаленных файлов.
922
- *
923
- * @param projectId ID проекта
924
- * @returns Mapping обфусцированных путей
925
- *
926
- * @example
927
- * ```typescript
928
- * const mapping = await sdk.projects.getFilePathMapping(projectId);
929
- * console.log(`Найдено ${mapping.files.length} файлов в проекте`);
930
- *
931
- * mapping.files.forEach(file => {
932
- * console.log(`${file.obfuscatedPath}: ${file.chunkCount} чанков`);
933
- * });
934
- * ```
310
+ * Получение маппинга файловых путей
935
311
  */
936
312
  async getFilePathMapping(projectId) {
937
313
  this.validateProjectId(projectId);
938
314
  const response = await this.httpClient.get(`/api/v1/projects/${projectId}/file-path-mapping`);
939
- // Конвертируем даты из строк в Date объекты
940
315
  return {
941
- success: response.success,
942
- files: response.files.map((file) => ({
943
- obfuscatedPath: file.obfuscatedPath,
944
- chunkCount: file.chunkCount,
945
- lastUpdated: new Date(file.lastUpdated)
946
- }))
316
+ success: response.success !== false,
317
+ mapping: response.mapping || [],
318
+ totalFiles: response.totalFiles || 0
947
319
  };
948
320
  }
321
+ // ===== УТИЛИТЫ =====
322
+ /**
323
+ * Валидация ID проекта
324
+ * @private
325
+ */
326
+ validateProjectId(projectId) {
327
+ if (projectId === undefined || projectId === null) {
328
+ throw new Error('Project ID is required');
329
+ }
330
+ if (typeof projectId !== 'string') {
331
+ throw new Error('Project ID must be a string');
332
+ }
333
+ if (projectId.trim().length === 0) {
334
+ throw new Error('Project ID cannot be empty');
335
+ }
336
+ }
949
337
  }
950
338
  //# sourceMappingURL=projects-api.js.map