Skip to main content

Search Memory

Search through stored memory entries using various criteria and filters.

Method

memory.search(query: SearchQuery): Promise<SearchResult>

Parameters

query
SearchQuery
required
The search query configuration
query.text
string
Text to search for in entry values and metadata
query.tags
string[]
Filter by specific tags
query.keys
string[]
Search within specific keys only
query.keyPattern
string
Regex pattern to match keys
query.metadata
Record<string, any>
Filter by metadata properties
query.dateRange
DateRange
Filter by creation date range
query.limit
number
default:"50"
Maximum number of results to return
query.offset
number
default:"0"
Number of results to skip (for pagination)
query.sortBy
string
default:"timestamp"
Field to sort by: timestamp, key, relevance
query.sortOrder
string
default:"desc"
Sort order: asc or desc

Response

results
MemoryEntry[]
Array of matching memory entries
total
number
Total number of matching entries
hasMore
boolean
Whether there are more results available
nextOffset
number
Offset for the next page of results
searchTime
number
Search execution time in milliseconds

Examples

import { Memory } from 'nebula-sdk';

const memory = new Memory({
  storageKey: 'user-session-123',
  apiKey: 'your-api-key'
});

// Search for entries containing specific text
const results = await memory.search({
  text: 'TypeScript programming',
  limit: 20
});

console.log(`Found ${results.total} entries`);
results.results.forEach(entry => {
  console.log(`${entry.key}: ${entry.value}`);
});

Search Patterns

// Search for conceptually similar content
async function semanticSearch(concept: string) {
  const results = await memory.search({
    text: concept,
    sortBy: 'relevance',
    limit: 10
  });
  
  // Further filter by semantic similarity
  return results.results.filter(entry => {
    const content = JSON.stringify(entry.value).toLowerCase();
    const conceptLower = concept.toLowerCase();
    
    // Simple semantic matching (can be enhanced with ML)
    const synonyms = {
      'programming': ['coding', 'development', 'software'],
      'learning': ['education', 'tutorial', 'training'],
      'error': ['bug', 'issue', 'problem']
    };
    
    return synonyms[conceptLower]?.some(synonym => 
      content.includes(synonym)
    ) || content.includes(conceptLower);
  });
}

const learningContent = await semanticSearch('learning');
// Build faceted search interface
async function getFacets() {
  // Get all entries to build facets
  const allResults = await memory.search({
    limit: 1000 // Adjust based on your data size
  });
  
  const facets = {
    tags: {},
    users: {},
    dateRanges: {}
  };
  
  allResults.results.forEach(entry => {
    // Count tags
    entry.tags?.forEach(tag => {
      facets.tags[tag] = (facets.tags[tag] || 0) + 1;
    });
    
    // Count users
    const userId = entry.metadata?.userId;
    if (userId) {
      facets.users[userId] = (facets.users[userId] || 0) + 1;
    }
    
    // Group by date ranges
    const date = new Date(entry.timestamp);
    const monthKey = `${date.getFullYear()}-${date.getMonth() + 1}`;
    facets.dateRanges[monthKey] = (facets.dateRanges[monthKey] || 0) + 1;
  });
  
  return facets;
}

// Use facets for filtering
const facets = await getFacets();
console.log('Available tags:', Object.keys(facets.tags));
// Implement pagination
async function paginatedSearch(query: any, page: number = 1, pageSize: number = 20) {
  const offset = (page - 1) * pageSize;
  
  const results = await memory.search({
    ...query,
    limit: pageSize,
    offset
  });
  
  return {
    data: results.results,
    pagination: {
      page,
      pageSize,
      total: results.total,
      totalPages: Math.ceil(results.total / pageSize),
      hasNext: results.hasMore,
      hasPrev: page > 1
    }
  };
}

// Usage
const page1 = await paginatedSearch({ tags: ['programming'] }, 1, 10);
const page2 = await paginatedSearch({ tags: ['programming'] }, 2, 10);

Search Optimization

// Build search index for better performance
class MemorySearchIndex {
  private index: Map<string, Set<string>> = new Map();
  
  async buildIndex() {
    const allEntries = await memory.search({ limit: 10000 });
    
    allEntries.results.forEach(entry => {
      // Index by tags
      entry.tags?.forEach(tag => {
        if (!this.index.has(tag)) {
          this.index.set(tag, new Set());
        }
        this.index.get(tag)!.add(entry.key);
      });
      
      // Index by text content
      const text = JSON.stringify(entry.value).toLowerCase();
      const words = text.split(/\W+/);
      
      words.forEach(word => {
        if (word.length > 2) { // Skip short words
          if (!this.index.has(word)) {
            this.index.set(word, new Set());
          }
          this.index.get(word)!.add(entry.key);
        }
      });
    });
  }
  
  async searchByIndex(terms: string[]): Promise<string[]> {
    const matchingSets = terms.map(term => 
      this.index.get(term.toLowerCase()) || new Set()
    );
    
    if (matchingSets.length === 0) return [];
    
    // Find intersection of all sets
    return Array.from(matchingSets.reduce((acc, set) => 
      new Set([...acc].filter(x => set.has(x)))
    ));
  }
}

const searchIndex = new MemorySearchIndex();
await searchIndex.buildIndex();
// Cache frequent searches
class CachedSearch {
  private cache = new Map<string, { result: any, timestamp: number }>();
  private cacheTTL = 5 * 60 * 1000; // 5 minutes
  
  private getCacheKey(query: any): string {
    return JSON.stringify(query);
  }
  
  async search(query: any) {
    const cacheKey = this.getCacheKey(query);
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.result;
    }
    
    const result = await memory.search(query);
    this.cache.set(cacheKey, {
      result,
      timestamp: Date.now()
    });
    
    return result;
  }
}

const cachedSearch = new CachedSearch();

Auto-complete

async function getAutocompleteSuggestions(partial: string) {
  const results = await memory.search({
    text: partial,
    limit: 10,
    sortBy: 'relevance'
  });
  
  // Extract unique suggestions
  const suggestions = new Set<string>();
  
  results.results.forEach(entry => {
    // Extract relevant terms from content
    const content = JSON.stringify(entry.value);
    const words = content.toLowerCase().split(/\W+/);
    
    words.forEach(word => {
      if (word.startsWith(partial.toLowerCase()) && word.length > partial.length) {
        suggestions.add(word);
      }
    });
    
    // Add tags that match
    entry.tags?.forEach(tag => {
      if (tag.toLowerCase().startsWith(partial.toLowerCase())) {
        suggestions.add(tag);
      }
    });
  });
  
  return Array.from(suggestions).slice(0, 10);
}

// Usage in UI
const suggestions = await getAutocompleteSuggestions('prog');
// Returns: ['programming', 'progress', 'program']
// Debounced live search
class LiveSearch {
  private debounceTimer: NodeJS.Timeout | null = null;
  private debounceDelay = 300; // ms
  
  search(query: string, callback: (results: any) => void) {
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
    
    this.debounceTimer = setTimeout(async () => {
      if (query.trim().length > 2) {
        const results = await memory.search({
          text: query,
          limit: 20,
          sortBy: 'relevance'
        });
        callback(results);
      }
    }, this.debounceDelay);
  }
}

const liveSearch = new LiveSearch();

// In your UI event handler
liveSearch.search(inputValue, (results) => {
  updateSearchResults(results);
});

Analytics and Insights

Search Analytics

// Track search patterns
class SearchAnalytics {
  async logSearch(query: any, resultCount: number) {
    await memory.store({
      key: `search_log_${Date.now()}`,
      value: {
        query,
        resultCount,
        timestamp: new Date()
      },
      tags: ['analytics', 'search'],
      ttl: 30 * 24 * 3600 // 30 days
    });
  }
  
  async getPopularSearches() {
    const logs = await memory.search({
      tags: ['analytics', 'search'],
      limit: 1000,
      sortBy: 'timestamp',
      sortOrder: 'desc'
    });
    
    const queryFreq = {};
    logs.results.forEach(log => {
      const queryText = log.value.query.text || 'empty';
      queryFreq[queryText] = (queryFreq[queryText] || 0) + 1;
    });
    
    return Object.entries(queryFreq)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 10);
  }
}

const analytics = new SearchAnalytics();

// Log each search
const results = await memory.search(query);
await analytics.logSearch(query, results.total);

Error Handling

async function robustSearch(query: any, retries: number = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await memory.search(query);
    } catch (error) {
      console.warn(`Search attempt ${i + 1} failed:`, error.message);
      
      if (i === retries - 1) {
        // Last attempt failed, return empty result
        return {
          results: [],
          total: 0,
          hasMore: false,
          nextOffset: 0,
          searchTime: 0
        };
      }
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Best Practices

  1. Use specific tags for better filtering
  2. Implement pagination for large result sets
  3. Cache frequent searches to improve performance
  4. Use debouncing for live search features
  5. Build search indices for complex queries
  6. Monitor search performance and optimize accordingly