bluera-knowledge 0.11.18 → 0.11.20
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/dist/{chunk-6FHWC36B.js → chunk-HRQD3MPH.js} +8 -6
- package/dist/chunk-HRQD3MPH.js.map +1 -0
- package/dist/{chunk-ZZNABJMQ.js → chunk-MQGRQ2EG.js} +99 -34
- package/dist/chunk-MQGRQ2EG.js.map +1 -0
- package/dist/{chunk-ZDEO4WJT.js → chunk-Q2ZGPJ66.js} +22 -70
- package/dist/chunk-Q2ZGPJ66.js.map +1 -0
- package/dist/{chunk-5NUI6JL6.js → chunk-ZSKQIMD7.js} +5 -2
- package/dist/chunk-ZSKQIMD7.js.map +1 -0
- package/dist/index.js +36 -18
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/watch.service-OPLKIDFQ.js +7 -0
- package/dist/workers/background-worker-cli.js +3 -3
- package/package.json +1 -1
- package/src/cli/commands/crawl.ts +1 -1
- package/src/cli/commands/index-cmd.test.ts +14 -4
- package/src/cli/commands/index-cmd.ts +11 -4
- package/src/cli/commands/store.test.ts +211 -18
- package/src/cli/commands/store.ts +26 -8
- package/src/crawl/article-converter.test.ts +30 -61
- package/src/crawl/article-converter.ts +2 -8
- package/src/crawl/bridge.test.ts +14 -0
- package/src/crawl/bridge.ts +17 -5
- package/src/crawl/intelligent-crawler.test.ts +65 -76
- package/src/crawl/intelligent-crawler.ts +33 -69
- package/src/db/lance.test.ts +3 -4
- package/src/db/lance.ts +14 -19
- package/src/mcp/server.test.ts +56 -1
- package/src/mcp/server.ts +5 -1
- package/src/plugin/git-clone.test.ts +44 -0
- package/src/plugin/git-clone.ts +4 -0
- package/src/services/code-unit.service.test.ts +59 -6
- package/src/services/code-unit.service.ts +47 -2
- package/src/services/index.ts +19 -3
- package/src/services/job.service.test.ts +10 -7
- package/src/services/job.service.ts +12 -6
- package/src/services/search.service.ts +15 -9
- package/src/services/services.test.ts +19 -6
- package/src/services/watch.service.test.ts +80 -56
- package/src/services/watch.service.ts +9 -6
- package/dist/chunk-5NUI6JL6.js.map +0 -1
- package/dist/chunk-6FHWC36B.js.map +0 -1
- package/dist/chunk-ZDEO4WJT.js.map +0 -1
- package/dist/chunk-ZZNABJMQ.js.map +0 -1
- package/dist/watch.service-BJV3TI3F.js +0 -7
- /package/dist/{watch.service-BJV3TI3F.js.map → watch.service-OPLKIDFQ.js.map} +0 -0
|
@@ -518,15 +518,21 @@ export class SearchService {
|
|
|
518
518
|
const results: SearchResult[] = [];
|
|
519
519
|
|
|
520
520
|
for (const storeId of stores) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
521
|
+
try {
|
|
522
|
+
const hits = await this.lanceStore.fullTextSearch(storeId, query, limit);
|
|
523
|
+
results.push(
|
|
524
|
+
...hits.map((r) => ({
|
|
525
|
+
id: r.id,
|
|
526
|
+
score: r.score,
|
|
527
|
+
content: r.content,
|
|
528
|
+
metadata: r.metadata,
|
|
529
|
+
}))
|
|
530
|
+
);
|
|
531
|
+
} catch {
|
|
532
|
+
// FTS index may not exist for this store - continue with other stores
|
|
533
|
+
// and rely on vector search results. This is expected behavior since
|
|
534
|
+
// FTS indexing is optional and hybrid search works with vector-only.
|
|
535
|
+
}
|
|
530
536
|
}
|
|
531
537
|
|
|
532
538
|
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
@@ -30,11 +30,12 @@ describe('destroyServices', () => {
|
|
|
30
30
|
expect(mockPythonBridge.stop).toHaveBeenCalledTimes(1);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('
|
|
33
|
+
it('throws on stop errors', async () => {
|
|
34
34
|
mockPythonBridge.stop.mockRejectedValue(new Error('stop failed'));
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
await expect(destroyServices(mockServices)).rejects.toThrow(
|
|
37
|
+
'Service shutdown failed: stop failed'
|
|
38
|
+
);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
it('is idempotent - multiple calls work correctly', async () => {
|
|
@@ -52,11 +53,23 @@ describe('destroyServices', () => {
|
|
|
52
53
|
expect(mockLance.close).not.toHaveBeenCalled();
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
it('
|
|
56
|
+
it('throws on LanceStore closeAsync errors', async () => {
|
|
56
57
|
mockLance.closeAsync.mockRejectedValue(new Error('closeAsync failed'));
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
await expect(destroyServices(mockServices)).rejects.toThrow(
|
|
60
|
+
'Service shutdown failed: closeAsync failed'
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('attempts all cleanup even if first fails, then throws aggregate', async () => {
|
|
65
|
+
mockLance.closeAsync.mockRejectedValue(new Error('lance failed'));
|
|
66
|
+
mockPythonBridge.stop.mockRejectedValue(new Error('bridge failed'));
|
|
67
|
+
|
|
68
|
+
await expect(destroyServices(mockServices)).rejects.toThrow();
|
|
69
|
+
|
|
70
|
+
// Both should have been called even though first failed
|
|
71
|
+
expect(mockLance.closeAsync).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(mockPythonBridge.stop).toHaveBeenCalledTimes(1);
|
|
60
73
|
});
|
|
61
74
|
|
|
62
75
|
it('waits for LanceStore async cleanup before returning', async () => {
|
|
@@ -75,10 +75,12 @@ describe('WatchService', () => {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
describe('watch - Lifecycle', () => {
|
|
78
|
+
const noopErrorHandler = (): void => {};
|
|
79
|
+
|
|
78
80
|
it('starts watching a file store', async () => {
|
|
79
81
|
const { watch } = await import('chokidar');
|
|
80
82
|
|
|
81
|
-
await watchService.watch(mockFileStore);
|
|
83
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
82
84
|
|
|
83
85
|
expect(watch).toHaveBeenCalledWith(
|
|
84
86
|
mockFileStore.path,
|
|
@@ -93,13 +95,13 @@ describe('WatchService', () => {
|
|
|
93
95
|
it('starts watching a repo store', async () => {
|
|
94
96
|
const { watch } = await import('chokidar');
|
|
95
97
|
|
|
96
|
-
await watchService.watch(mockRepoStore);
|
|
98
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
97
99
|
|
|
98
100
|
expect(watch).toHaveBeenCalledWith(mockRepoStore.path, expect.any(Object));
|
|
99
101
|
});
|
|
100
102
|
|
|
101
103
|
it('sets up event handlers on watcher', async () => {
|
|
102
|
-
await watchService.watch(mockFileStore);
|
|
104
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
103
105
|
|
|
104
106
|
const watcher = mockWatchers[0];
|
|
105
107
|
expect(watcher?.on).toHaveBeenCalledWith('all', expect.any(Function));
|
|
@@ -109,10 +111,10 @@ describe('WatchService', () => {
|
|
|
109
111
|
it('does not start watching if already watching the same store', async () => {
|
|
110
112
|
const { watch } = await import('chokidar');
|
|
111
113
|
|
|
112
|
-
await watchService.watch(mockFileStore);
|
|
114
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
113
115
|
const callCount1 = (watch as ReturnType<typeof vi.fn>).mock.calls.length;
|
|
114
116
|
|
|
115
|
-
await watchService.watch(mockFileStore);
|
|
117
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
116
118
|
const callCount2 = (watch as ReturnType<typeof vi.fn>).mock.calls.length;
|
|
117
119
|
|
|
118
120
|
expect(callCount2).toBe(callCount1);
|
|
@@ -121,17 +123,17 @@ describe('WatchService', () => {
|
|
|
121
123
|
it('allows watching multiple different stores', async () => {
|
|
122
124
|
const { watch } = await import('chokidar');
|
|
123
125
|
|
|
124
|
-
await watchService.watch(mockFileStore);
|
|
125
|
-
await watchService.watch(mockRepoStore);
|
|
126
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
127
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
126
128
|
|
|
127
129
|
expect(watch).toHaveBeenCalledTimes(2);
|
|
128
130
|
});
|
|
129
131
|
|
|
130
132
|
it('resolves immediately when store is already being watched', async () => {
|
|
131
|
-
await watchService.watch(mockFileStore);
|
|
133
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
132
134
|
|
|
133
135
|
const startTime = Date.now();
|
|
134
|
-
await watchService.watch(mockFileStore);
|
|
136
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
135
137
|
const endTime = Date.now();
|
|
136
138
|
|
|
137
139
|
// Should resolve immediately
|
|
@@ -140,8 +142,10 @@ describe('WatchService', () => {
|
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
describe('watch - Debounce Logic', () => {
|
|
145
|
+
const noopErrorHandler = (): void => {};
|
|
146
|
+
|
|
143
147
|
it('debounces rapid file changes with default timeout', async () => {
|
|
144
|
-
await watchService.watch(mockFileStore);
|
|
148
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
145
149
|
|
|
146
150
|
const watcher = mockWatchers[0];
|
|
147
151
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -236,8 +240,10 @@ describe('WatchService', () => {
|
|
|
236
240
|
});
|
|
237
241
|
|
|
238
242
|
describe('watch - Reindexing', () => {
|
|
243
|
+
const noopErrorHandler = (): void => {};
|
|
244
|
+
|
|
239
245
|
it('initializes lance store before reindexing', async () => {
|
|
240
|
-
await watchService.watch(mockFileStore);
|
|
246
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
241
247
|
|
|
242
248
|
const watcher = mockWatchers[0];
|
|
243
249
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -255,7 +261,7 @@ describe('WatchService', () => {
|
|
|
255
261
|
});
|
|
256
262
|
|
|
257
263
|
it('calls indexStore with correct store', async () => {
|
|
258
|
-
await watchService.watch(mockFileStore);
|
|
264
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
259
265
|
|
|
260
266
|
const watcher = mockWatchers[0];
|
|
261
267
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -272,7 +278,7 @@ describe('WatchService', () => {
|
|
|
272
278
|
it('calls onReindex callback after successful reindex', async () => {
|
|
273
279
|
const onReindex = vi.fn();
|
|
274
280
|
|
|
275
|
-
await watchService.watch(mockFileStore, 1000, onReindex);
|
|
281
|
+
await watchService.watch(mockFileStore, 1000, onReindex, noopErrorHandler);
|
|
276
282
|
|
|
277
283
|
const watcher = mockWatchers[0];
|
|
278
284
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -287,7 +293,7 @@ describe('WatchService', () => {
|
|
|
287
293
|
});
|
|
288
294
|
|
|
289
295
|
it('does not call onReindex if not provided', async () => {
|
|
290
|
-
await watchService.watch(mockFileStore);
|
|
296
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
291
297
|
|
|
292
298
|
const watcher = mockWatchers[0];
|
|
293
299
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -303,8 +309,8 @@ describe('WatchService', () => {
|
|
|
303
309
|
});
|
|
304
310
|
|
|
305
311
|
it('handles concurrent reindexing for different stores', async () => {
|
|
306
|
-
await watchService.watch(mockFileStore);
|
|
307
|
-
await watchService.watch(mockRepoStore);
|
|
312
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
313
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
308
314
|
|
|
309
315
|
const watcher1 = mockWatchers[0];
|
|
310
316
|
const watcher2 = mockWatchers[1];
|
|
@@ -330,12 +336,13 @@ describe('WatchService', () => {
|
|
|
330
336
|
});
|
|
331
337
|
|
|
332
338
|
describe('watch - Error Handling', () => {
|
|
333
|
-
it('
|
|
334
|
-
const
|
|
339
|
+
it('calls onError when reindexing fails', async () => {
|
|
340
|
+
const onError = vi.fn();
|
|
341
|
+
const indexError = new Error('Index failed');
|
|
335
342
|
|
|
336
|
-
mockIndexService.indexStore = vi.fn().mockRejectedValue(
|
|
343
|
+
mockIndexService.indexStore = vi.fn().mockRejectedValue(indexError);
|
|
337
344
|
|
|
338
|
-
await watchService.watch(mockFileStore);
|
|
345
|
+
await watchService.watch(mockFileStore, 1000, undefined, onError);
|
|
339
346
|
|
|
340
347
|
const watcher = mockWatchers[0];
|
|
341
348
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -346,17 +353,16 @@ describe('WatchService', () => {
|
|
|
346
353
|
vi.advanceTimersByTime(1100);
|
|
347
354
|
await vi.runAllTimersAsync();
|
|
348
355
|
|
|
349
|
-
expect(
|
|
350
|
-
|
|
351
|
-
consoleErrorSpy.mockRestore();
|
|
356
|
+
expect(onError).toHaveBeenCalledWith(indexError);
|
|
352
357
|
});
|
|
353
358
|
|
|
354
|
-
it('
|
|
355
|
-
const
|
|
359
|
+
it('calls onError when lance initialization fails', async () => {
|
|
360
|
+
const onError = vi.fn();
|
|
361
|
+
const initError = new Error('Init failed');
|
|
356
362
|
|
|
357
|
-
mockLanceStore.initialize = vi.fn().mockRejectedValue(
|
|
363
|
+
mockLanceStore.initialize = vi.fn().mockRejectedValue(initError);
|
|
358
364
|
|
|
359
|
-
await watchService.watch(mockFileStore);
|
|
365
|
+
await watchService.watch(mockFileStore, 1000, undefined, onError);
|
|
360
366
|
|
|
361
367
|
const watcher = mockWatchers[0];
|
|
362
368
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -367,15 +373,13 @@ describe('WatchService', () => {
|
|
|
367
373
|
vi.advanceTimersByTime(1100);
|
|
368
374
|
await vi.runAllTimersAsync();
|
|
369
375
|
|
|
370
|
-
expect(
|
|
371
|
-
|
|
372
|
-
consoleErrorSpy.mockRestore();
|
|
376
|
+
expect(onError).toHaveBeenCalledWith(initError);
|
|
373
377
|
});
|
|
374
378
|
|
|
375
|
-
it('
|
|
376
|
-
const
|
|
379
|
+
it('calls onError for watcher errors', async () => {
|
|
380
|
+
const onError = vi.fn();
|
|
377
381
|
|
|
378
|
-
await watchService.watch(mockFileStore);
|
|
382
|
+
await watchService.watch(mockFileStore, 1000, undefined, onError);
|
|
379
383
|
|
|
380
384
|
const watcher = mockWatchers[0];
|
|
381
385
|
const errorHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -385,13 +389,29 @@ describe('WatchService', () => {
|
|
|
385
389
|
const testError = new Error('Watcher error');
|
|
386
390
|
errorHandler?.(testError);
|
|
387
391
|
|
|
388
|
-
expect(
|
|
392
|
+
expect(onError).toHaveBeenCalledWith(testError);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('requires onError callback to be provided', async () => {
|
|
396
|
+
// onError is now required - this test verifies the type signature enforces it
|
|
397
|
+
// TypeScript would catch this at compile time, but runtime behavior should still work
|
|
398
|
+
const onError = vi.fn();
|
|
399
|
+
await watchService.watch(mockFileStore, 1000, undefined, onError);
|
|
400
|
+
|
|
401
|
+
const watcher = mockWatchers[0];
|
|
402
|
+
const errorHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
403
|
+
(call: unknown[]) => call[0] === 'error'
|
|
404
|
+
)?.[1] as ((error: Error) => void) | undefined;
|
|
405
|
+
|
|
406
|
+
const testError = new Error('Watcher error');
|
|
407
|
+
errorHandler?.(testError);
|
|
389
408
|
|
|
390
|
-
|
|
409
|
+
// Error should be passed to onError, not thrown
|
|
410
|
+
expect(onError).toHaveBeenCalledWith(testError);
|
|
391
411
|
});
|
|
392
412
|
|
|
393
413
|
it('continues watching after error during reindex', async () => {
|
|
394
|
-
const
|
|
414
|
+
const onError = vi.fn();
|
|
395
415
|
|
|
396
416
|
// First call fails
|
|
397
417
|
mockIndexService.indexStore = vi
|
|
@@ -399,7 +419,7 @@ describe('WatchService', () => {
|
|
|
399
419
|
.mockRejectedValueOnce(new Error('First fail'))
|
|
400
420
|
.mockResolvedValueOnce({ ok: true });
|
|
401
421
|
|
|
402
|
-
await watchService.watch(mockFileStore);
|
|
422
|
+
await watchService.watch(mockFileStore, 1000, undefined, onError);
|
|
403
423
|
|
|
404
424
|
const watcher = mockWatchers[0];
|
|
405
425
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -417,15 +437,15 @@ describe('WatchService', () => {
|
|
|
417
437
|
await vi.runAllTimersAsync();
|
|
418
438
|
|
|
419
439
|
expect(mockIndexService.indexStore).toHaveBeenCalledTimes(2);
|
|
420
|
-
expect(
|
|
421
|
-
|
|
422
|
-
consoleErrorSpy.mockRestore();
|
|
440
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
423
441
|
});
|
|
424
442
|
});
|
|
425
443
|
|
|
426
444
|
describe('unwatch', () => {
|
|
445
|
+
const noopErrorHandler = (): void => {};
|
|
446
|
+
|
|
427
447
|
it('stops watching a store', async () => {
|
|
428
|
-
await watchService.watch(mockFileStore);
|
|
448
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
429
449
|
|
|
430
450
|
const watcher = mockWatchers[0];
|
|
431
451
|
await watchService.unwatch(mockFileStore.id);
|
|
@@ -436,11 +456,11 @@ describe('WatchService', () => {
|
|
|
436
456
|
it('removes watcher from internal map', async () => {
|
|
437
457
|
const { watch } = await import('chokidar');
|
|
438
458
|
|
|
439
|
-
await watchService.watch(mockFileStore);
|
|
459
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
440
460
|
await watchService.unwatch(mockFileStore.id);
|
|
441
461
|
|
|
442
462
|
// Trying to watch again should create new watcher
|
|
443
|
-
await watchService.watch(mockFileStore);
|
|
463
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
444
464
|
|
|
445
465
|
expect(watch).toHaveBeenCalledTimes(2);
|
|
446
466
|
});
|
|
@@ -451,8 +471,8 @@ describe('WatchService', () => {
|
|
|
451
471
|
});
|
|
452
472
|
|
|
453
473
|
it('does not affect other watchers', async () => {
|
|
454
|
-
await watchService.watch(mockFileStore);
|
|
455
|
-
await watchService.watch(mockRepoStore);
|
|
474
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
475
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
456
476
|
|
|
457
477
|
const watcher1 = mockWatchers[0];
|
|
458
478
|
const watcher2 = mockWatchers[1];
|
|
@@ -464,7 +484,7 @@ describe('WatchService', () => {
|
|
|
464
484
|
});
|
|
465
485
|
|
|
466
486
|
it('clears pending timeout to prevent timer leak', async () => {
|
|
467
|
-
await watchService.watch(mockFileStore, 1000);
|
|
487
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
468
488
|
|
|
469
489
|
const watcher = mockWatchers[0];
|
|
470
490
|
const allHandler = (watcher?.on as ReturnType<typeof vi.fn>).mock.calls.find(
|
|
@@ -487,9 +507,11 @@ describe('WatchService', () => {
|
|
|
487
507
|
});
|
|
488
508
|
|
|
489
509
|
describe('unwatchAll', () => {
|
|
510
|
+
const noopErrorHandler = (): void => {};
|
|
511
|
+
|
|
490
512
|
it('stops all watchers', async () => {
|
|
491
|
-
await watchService.watch(mockFileStore);
|
|
492
|
-
await watchService.watch(mockRepoStore);
|
|
513
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
514
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
493
515
|
|
|
494
516
|
const watcher1 = mockWatchers[0];
|
|
495
517
|
const watcher2 = mockWatchers[1];
|
|
@@ -503,13 +525,13 @@ describe('WatchService', () => {
|
|
|
503
525
|
it('clears all watchers from map', async () => {
|
|
504
526
|
const { watch } = await import('chokidar');
|
|
505
527
|
|
|
506
|
-
await watchService.watch(mockFileStore);
|
|
507
|
-
await watchService.watch(mockRepoStore);
|
|
528
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
529
|
+
await watchService.watch(mockRepoStore, 1000, undefined, noopErrorHandler);
|
|
508
530
|
|
|
509
531
|
await watchService.unwatchAll();
|
|
510
532
|
|
|
511
533
|
// Watching again should create new watchers
|
|
512
|
-
await watchService.watch(mockFileStore);
|
|
534
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
513
535
|
|
|
514
536
|
expect(watch).toHaveBeenCalledTimes(3); // 2 initial + 1 after unwatchAll
|
|
515
537
|
});
|
|
@@ -521,10 +543,12 @@ describe('WatchService', () => {
|
|
|
521
543
|
});
|
|
522
544
|
|
|
523
545
|
describe('File Watching Configuration', () => {
|
|
546
|
+
const noopErrorHandler = (): void => {};
|
|
547
|
+
|
|
524
548
|
it('ignores .git directories', async () => {
|
|
525
549
|
const { watch } = await import('chokidar');
|
|
526
550
|
|
|
527
|
-
await watchService.watch(mockFileStore);
|
|
551
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
528
552
|
|
|
529
553
|
const config = (watch as ReturnType<typeof vi.fn>).mock.calls[0]?.[1];
|
|
530
554
|
expect(config.ignored).toBeInstanceOf(RegExp);
|
|
@@ -535,7 +559,7 @@ describe('WatchService', () => {
|
|
|
535
559
|
it('ignores node_modules directories', async () => {
|
|
536
560
|
const { watch } = await import('chokidar');
|
|
537
561
|
|
|
538
|
-
await watchService.watch(mockFileStore);
|
|
562
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
539
563
|
|
|
540
564
|
const config = (watch as ReturnType<typeof vi.fn>).mock.calls[0]?.[1];
|
|
541
565
|
expect(config.ignored.test('.node_modules')).toBe(true);
|
|
@@ -545,7 +569,7 @@ describe('WatchService', () => {
|
|
|
545
569
|
it('ignores dist and build directories', async () => {
|
|
546
570
|
const { watch } = await import('chokidar');
|
|
547
571
|
|
|
548
|
-
await watchService.watch(mockFileStore);
|
|
572
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
549
573
|
|
|
550
574
|
const config = (watch as ReturnType<typeof vi.fn>).mock.calls[0]?.[1];
|
|
551
575
|
expect(config.ignored.test('.dist')).toBe(true);
|
|
@@ -555,7 +579,7 @@ describe('WatchService', () => {
|
|
|
555
579
|
it('sets persistent to true', async () => {
|
|
556
580
|
const { watch } = await import('chokidar');
|
|
557
581
|
|
|
558
|
-
await watchService.watch(mockFileStore);
|
|
582
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
559
583
|
|
|
560
584
|
const config = (watch as ReturnType<typeof vi.fn>).mock.calls[0]?.[1];
|
|
561
585
|
expect(config.persistent).toBe(true);
|
|
@@ -564,7 +588,7 @@ describe('WatchService', () => {
|
|
|
564
588
|
it('sets ignoreInitial to true', async () => {
|
|
565
589
|
const { watch } = await import('chokidar');
|
|
566
590
|
|
|
567
|
-
await watchService.watch(mockFileStore);
|
|
591
|
+
await watchService.watch(mockFileStore, 1000, undefined, noopErrorHandler);
|
|
568
592
|
|
|
569
593
|
const config = (watch as ReturnType<typeof vi.fn>).mock.calls[0]?.[1];
|
|
570
594
|
expect(config.ignoreInitial).toBe(true);
|
|
@@ -16,8 +16,9 @@ export class WatchService {
|
|
|
16
16
|
|
|
17
17
|
async watch(
|
|
18
18
|
store: FileStore | RepoStore,
|
|
19
|
-
debounceMs
|
|
20
|
-
onReindex
|
|
19
|
+
debounceMs: number,
|
|
20
|
+
onReindex: (() => void) | undefined,
|
|
21
|
+
onError: (error: Error) => void
|
|
21
22
|
): Promise<void> {
|
|
22
23
|
if (this.watchers.has(store.id)) {
|
|
23
24
|
return Promise.resolve(); // Already watching
|
|
@@ -40,8 +41,9 @@ export class WatchService {
|
|
|
40
41
|
await this.lanceStore.initialize(store.id);
|
|
41
42
|
await this.indexService.indexStore(store);
|
|
42
43
|
onReindex?.();
|
|
43
|
-
} catch (
|
|
44
|
-
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
46
|
+
onError(error);
|
|
45
47
|
}
|
|
46
48
|
})();
|
|
47
49
|
}, debounceMs);
|
|
@@ -50,8 +52,9 @@ export class WatchService {
|
|
|
50
52
|
|
|
51
53
|
watcher.on('all', reindexHandler);
|
|
52
54
|
|
|
53
|
-
watcher.on('error', (
|
|
54
|
-
|
|
55
|
+
watcher.on('error', (e) => {
|
|
56
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
57
|
+
onError(error);
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
this.watchers.set(store.id, watcher);
|