371 lines
8.6 KiB
Markdown
371 lines
8.6 KiB
Markdown
# Search Migration Guide
|
|
|
|
## Overview
|
|
|
|
This guide helps you migrate from the old `SearchEvaluatorService` to the new `SearchOrchestratorService`.
|
|
|
|
## Why Migrate?
|
|
|
|
The new orchestrator provides:
|
|
- ✅ **Correct filtering**: All field operators actually work
|
|
- ✅ **Better highlighting**: Precise ranges instead of text matching
|
|
- ✅ **More features**: Context lines, max results, match ranges
|
|
- ✅ **Better performance**: Pre-calculated ranges, no rescanning
|
|
|
|
## Quick Migration
|
|
|
|
### Before (Old)
|
|
```typescript
|
|
import { SearchEvaluatorService } from './core/search/search-evaluator.service';
|
|
|
|
constructor(private evaluator: SearchEvaluatorService) {}
|
|
|
|
search(query: string) {
|
|
const results = this.evaluator.search(query, {
|
|
caseSensitive: false
|
|
});
|
|
|
|
// Results don't include ranges
|
|
results.forEach(result => {
|
|
result.matches.forEach(match => {
|
|
// match.startOffset and match.endOffset are basic
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
### After (New)
|
|
```typescript
|
|
import { SearchOrchestratorService } from './core/search/search-orchestrator.service';
|
|
|
|
constructor(private orchestrator: SearchOrchestratorService) {}
|
|
|
|
search(query: string) {
|
|
const results = this.orchestrator.execute(query, {
|
|
caseSensitive: false,
|
|
contextLines: 5, // NEW: Adjustable context
|
|
maxResults: 100 // NEW: Limit results
|
|
});
|
|
|
|
// Results include precise ranges
|
|
results.forEach(result => {
|
|
result.matches.forEach(match => {
|
|
// match.ranges: MatchRange[] with start/end/line/context
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
## Step-by-Step Migration
|
|
|
|
### 1. Update Imports
|
|
|
|
```typescript
|
|
// OLD
|
|
import { SearchEvaluatorService, SearchResult } from './core/search/search-evaluator.service';
|
|
|
|
// NEW
|
|
import { SearchOrchestratorService, SearchResult } from './core/search/search-orchestrator.service';
|
|
```
|
|
|
|
### 2. Update Injection
|
|
|
|
```typescript
|
|
// OLD
|
|
constructor(private evaluator: SearchEvaluatorService) {}
|
|
|
|
// NEW
|
|
constructor(private orchestrator: SearchOrchestratorService) {}
|
|
```
|
|
|
|
### 3. Update Method Calls
|
|
|
|
```typescript
|
|
// OLD
|
|
const results = this.evaluator.search(query, options);
|
|
|
|
// NEW
|
|
const results = this.orchestrator.execute(query, options);
|
|
```
|
|
|
|
### 4. Update Result Handling
|
|
|
|
```typescript
|
|
// OLD
|
|
results.forEach(result => {
|
|
const { noteId, matches, score } = result;
|
|
// matches[].startOffset, matches[].endOffset
|
|
});
|
|
|
|
// NEW
|
|
results.forEach(result => {
|
|
const { noteId, matches, score, allRanges } = result;
|
|
// matches[].ranges: MatchRange[]
|
|
// allRanges: MatchRange[] (all ranges in note)
|
|
});
|
|
```
|
|
|
|
## Highlighting Migration
|
|
|
|
### Before (Manual)
|
|
```typescript
|
|
highlightMatch(context: string, matchText: string): string {
|
|
const regex = new RegExp(`(${matchText})`, 'gi');
|
|
return context.replace(regex, '<mark>$1</mark>');
|
|
}
|
|
```
|
|
|
|
### After (Service)
|
|
```typescript
|
|
import { SearchHighlighterService } from './core/search/search-highlighter.service';
|
|
|
|
constructor(private highlighter: SearchHighlighterService) {}
|
|
|
|
highlightMatch(match: SearchMatch): string {
|
|
// Use ranges for precise highlighting
|
|
if (match.ranges && match.ranges.length > 0) {
|
|
return this.highlighter.highlightWithRanges(match.context, match.ranges);
|
|
}
|
|
|
|
// Fallback to text-based
|
|
return this.highlighter.highlightMatches(match.context, [match.text], false);
|
|
}
|
|
```
|
|
|
|
## Preferences Migration
|
|
|
|
### Before (Manual State)
|
|
```typescript
|
|
export class MyComponent {
|
|
collapseResults = false;
|
|
showMoreContext = false;
|
|
|
|
// Manual localStorage
|
|
ngOnInit() {
|
|
const saved = localStorage.getItem('my-prefs');
|
|
if (saved) {
|
|
const prefs = JSON.parse(saved);
|
|
this.collapseResults = prefs.collapse;
|
|
}
|
|
}
|
|
|
|
savePrefs() {
|
|
localStorage.setItem('my-prefs', JSON.stringify({
|
|
collapse: this.collapseResults
|
|
}));
|
|
}
|
|
}
|
|
```
|
|
|
|
### After (Service)
|
|
```typescript
|
|
import { SearchPreferencesService } from './core/search/search-preferences.service';
|
|
|
|
export class MyComponent {
|
|
constructor(private preferences: SearchPreferencesService) {}
|
|
|
|
collapseResults = false;
|
|
showMoreContext = false;
|
|
|
|
ngOnInit() {
|
|
// Auto-load preferences
|
|
const prefs = this.preferences.getPreferences('my-context');
|
|
this.collapseResults = prefs.collapseResults;
|
|
this.showMoreContext = prefs.showMoreContext;
|
|
}
|
|
|
|
onToggleCollapse() {
|
|
// Auto-save preferences
|
|
this.preferences.updatePreferences('my-context', {
|
|
collapseResults: this.collapseResults
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
## Component Migration
|
|
|
|
### SearchResultsComponent
|
|
|
|
#### Before
|
|
```typescript
|
|
<app-search-results
|
|
[results]="results()"
|
|
(noteOpen)="onNoteOpen($event)"
|
|
/>
|
|
```
|
|
|
|
#### After
|
|
```typescript
|
|
<app-search-results
|
|
[results]="results()"
|
|
[collapseAll]="collapseResults"
|
|
[showMoreContext]="showMoreContext"
|
|
[contextLines]="contextLines()"
|
|
(noteOpen)="onNoteOpen($event)"
|
|
/>
|
|
```
|
|
|
|
### SearchPanelComponent
|
|
|
|
No changes needed! The component now includes toggles automatically.
|
|
|
|
```typescript
|
|
<app-search-panel
|
|
placeholder="Search in vault..."
|
|
context="vault"
|
|
(noteOpen)="openNote($event)"
|
|
/>
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Pattern 1: Search with Context
|
|
|
|
```typescript
|
|
// OLD: Fixed context
|
|
const results = this.evaluator.search(query);
|
|
|
|
// NEW: Adjustable context
|
|
const results = this.orchestrator.execute(query, {
|
|
contextLines: this.showMoreContext ? 5 : 2
|
|
});
|
|
```
|
|
|
|
### Pattern 2: Limit Results
|
|
|
|
```typescript
|
|
// OLD: Manual slicing
|
|
const results = this.evaluator.search(query).slice(0, 100);
|
|
|
|
// NEW: Built-in limit
|
|
const results = this.orchestrator.execute(query, {
|
|
maxResults: 100
|
|
});
|
|
```
|
|
|
|
### Pattern 3: Highlighting
|
|
|
|
```typescript
|
|
// OLD: Manual regex
|
|
const highlighted = text.replace(
|
|
new RegExp(`(${term})`, 'gi'),
|
|
'<mark>$1</mark>'
|
|
);
|
|
|
|
// NEW: Service with XSS protection
|
|
const highlighted = this.highlighter.highlightMatches(
|
|
text,
|
|
[term],
|
|
caseSensitive
|
|
);
|
|
```
|
|
|
|
## Testing Migration
|
|
|
|
### Before
|
|
```typescript
|
|
it('should search', () => {
|
|
const results = evaluator.search('test');
|
|
expect(results.length).toBeGreaterThan(0);
|
|
});
|
|
```
|
|
|
|
### After
|
|
```typescript
|
|
it('should search with orchestrator', () => {
|
|
const results = orchestrator.execute('test');
|
|
expect(results.length).toBeGreaterThan(0);
|
|
expect(results[0].allRanges).toBeDefined();
|
|
expect(results[0].matches[0].ranges).toBeDefined();
|
|
});
|
|
```
|
|
|
|
## Backward Compatibility
|
|
|
|
The old `SearchEvaluatorService` still works as a **wrapper**:
|
|
|
|
```typescript
|
|
// This still works (delegates to orchestrator)
|
|
const results = this.evaluator.search(query, options);
|
|
|
|
// But you won't get the new features:
|
|
// - No contextLines option
|
|
// - No maxResults option
|
|
// - No allRanges in results
|
|
// - matches[].ranges are converted from allRanges[0]
|
|
```
|
|
|
|
## Breaking Changes
|
|
|
|
**None!** All existing code continues to work.
|
|
|
|
## Deprecation Timeline
|
|
|
|
- **Now**: `SearchEvaluatorService` marked as `@deprecated`
|
|
- **v2.0**: `SearchEvaluatorService` will be removed
|
|
- **Migration window**: ~6 months
|
|
|
|
## Checklist
|
|
|
|
- [ ] Update imports to `SearchOrchestratorService`
|
|
- [ ] Update injection in constructors
|
|
- [ ] Replace `.search()` with `.execute()`
|
|
- [ ] Add `SearchHighlighterService` for highlighting
|
|
- [ ] Add `SearchPreferencesService` for preferences
|
|
- [ ] Update component inputs (collapseAll, showMoreContext, contextLines)
|
|
- [ ] Update tests to check for ranges
|
|
- [ ] Remove manual localStorage code
|
|
- [ ] Test all search scenarios
|
|
|
|
## Need Help?
|
|
|
|
- **Documentation**: `src/core/search/README.md`
|
|
- **Examples**: `src/components/search-panel/search-panel.component.ts`
|
|
- **Tests**: `src/core/search/*.spec.ts`
|
|
- **Issues**: Create a GitHub issue with `[search]` prefix
|
|
|
|
## FAQ
|
|
|
|
### Q: Do I have to migrate immediately?
|
|
**A:** No, the old service still works. But you won't get the bug fixes and new features.
|
|
|
|
### Q: Will my existing code break?
|
|
**A:** No, backward compatibility is maintained.
|
|
|
|
### Q: What if I only want highlighting?
|
|
**A:** You can use `SearchHighlighterService` independently:
|
|
|
|
```typescript
|
|
import { SearchHighlighterService } from './core/search/search-highlighter.service';
|
|
|
|
constructor(private highlighter: SearchHighlighterService) {}
|
|
|
|
highlight(text: string, terms: string[]) {
|
|
return this.highlighter.highlightMatches(text, terms, false);
|
|
}
|
|
```
|
|
|
|
### Q: What if I only want preferences?
|
|
**A:** You can use `SearchPreferencesService` independently:
|
|
|
|
```typescript
|
|
import { SearchPreferencesService } from './core/search/search-preferences.service';
|
|
|
|
constructor(private preferences: SearchPreferencesService) {}
|
|
|
|
loadPrefs() {
|
|
return this.preferences.getPreferences('my-context');
|
|
}
|
|
```
|
|
|
|
### Q: Can I mix old and new?
|
|
**A:** Yes, but not recommended. Stick to one approach per component.
|
|
|
|
## Examples
|
|
|
|
See complete examples in:
|
|
- `src/components/search-panel/search-panel.component.ts`
|
|
- `src/components/search-results/search-results.component.ts`
|
|
- `src/core/search/*.spec.ts`
|