MiniSearch

repository·master·Indexed 27 days ago

https://github.com/lucaong/minisearch

A lightweight, in-memory full-text search engine for JavaScript that works in Node.js and the browser. Version 7.2.0 supports prefix search, fuzzy matching, and auto-suggestions using a BM25-based scoring algorithm. It is designed for resource-constrained environments and provides a simple API for indexing documents, performing queries, and managing data via methods like addAll(), search(), and vacuum().

Tokens
5.1K
Snippets
8
Records
28
Agent score
89%

What's inside minisearch

  1. Overview of MiniSearch architecture and use cases

    master

    MiniSearch is designed for providing rich full-text search functionalities in local setups (e.g., client-side in the browser).

    Core Goals:

    • Small memory footprint for the index.
    • Fast document indexing.
    • Versatile and performant search features.
    • Simple API surface.
    • Support for adding/removing documents at any time.

    Ideal Use Cases:

    • Client-side search in web applications.
    • Small to medium-sized datasets where memory is constrained.

    Not Recommended For:

    • Large index data structures that exceed local memory.
    • Distributed setups requiring synchronized nodes.
    • Scenarios requiring built-in, opinionated locale support (stemmers, stopwords, etc.). MiniSearch provides the building blocks for you to implement these yourself.
  2. Run the MiniSearch plain JavaScript demo

    master

    To run the client-side demonstration application locally, follow these steps:

    1. Open a terminal and cd to the docs/demo directory.
    2. Start a local HTTP server using one of the following commands:
      • If you have Python installed: python3 -m http.server
      • If you have NodeJS installed: npx http-server -p 8000
    3. Open your browser and navigate to the server URL (e.g., http://localhost:8000).
    python3 -m http.server
    # OR
    npx http-server -p 8000
  3. Basic usage of MiniSearch

    master

    To use MiniSearch, initialize it with fields (the keys to index for full-text search) and storeFields (the keys to include in the search results). Use addAll() to index a collection of documents, and search() to perform a query.

    const documents = [
      { id: 1, title: 'Moby Dick', text: 'Call me Ishmael. Some years ago...', category: 'fiction' },
      { id: 2, title: 'Zen and the Art of Motorcycle Maintenance', text: 'I can see by my watch...', category: 'fiction' },
      { id: 3, title: 'Neuromancer', text: 'The sky above the port was...', category: 'fiction' },
      { id: 4, title: 'Zen and the Art of Archery', text: 'At first sight it must seem...', category: 'non-fiction' }
    ]
    
    let miniSearch = new MiniSearch({
      fields: ['title', 'text'], // fields to index for full-text search
      storeFields: ['title', 'category'] // fields to return with search results
    })
    
    // Index all documents
    miniSearch.addAll(documents)
    
    // Search with default options
    let results = miniSearch.search('zen art motorcycle')
  4. Initialize MiniSearch

    master

    Create a new MiniSearch instance by providing an Options object. At minimum, you must specify the fields to be indexed. You can also define an idField (defaults to 'id'), storeFields to include data in search results, and custom functions for field extraction, tokenization, and term processing.

    // Basic initialization
    const miniSearch = new MiniSearch({
      fields: ['title', 'text']
    });
    
    // Initialization with custom ID field and stored fields
    const miniSearch = new MiniSearch({
      idField: 'key',
      fields: ['title', 'text'],
      storeFields: ['title', 'category']
    });
  5. Generate auto-suggestions

    master
    Use autoSuggest(query, options) to suggest search queries based on an incomplete input. Suggestions are ranked by the relevance of the documents they would return. The autoSuggest method accepts the same options as the search method, including fuzzy and filter.
  6. Advanced search options

    master

    The search method accepts an options object to customize query behavior:

    • fields: Search only specific fields.
    • boost: Increase the weight of specific fields.
    • prefix: Enable prefix search (e.g., 'moto' matches 'motorcycle').
    • filter: A function to filter results (e.g., by category).
    • fuzzy: Enable fuzzy matching with a value representing the max edit distance (e.g., 0.2).

    You can also set these as defaults during initialization using the searchOptions key.

    // Search only specific fields
    miniSearch.search('zen', { fields: ['title'] })
    
    // Boost some fields
    miniSearch.search('zen', { boost: { title: 2 } })
    
    // Prefix search
    miniSearch.search('moto', { prefix: true })
    
    // Filter results
    miniSearch.search('zen', {
      filter: (result) => result.category === 'fiction'
    })
    
    // Fuzzy search
    miniSearch.search('ismael', { fuzzy: 0.2 })
    
    // Set default search options upon initialization
    miniSearch = new MiniSearch({
      fields: ['title', 'text'],
      searchOptions: {
        boost: { title: 2 },
        fuzzy: 0.2
      }
    })
  7. Customize term processing

    master

    Use the processTerm option to normalize, filter, or apply stemming to terms. The function receives (term, fieldName) and should return the processed string or a falsy value to discard the term. Terms are downcased by default.

    let stopWords = new Set(['and', 'or', 'to', 'in', 'a', 'the'])
    
    let miniSearch = new MiniSearch({
      fields: ['title', 'text'],
      processTerm: (term, _fieldName) =>
        stopWords.has(term) ? null : term.toLowerCase()
    })
    
    // Use different processing for search queries
    let miniSearch = new MiniSearch({
      fields: ['title', 'text'],
      processTerm: (term, _fieldName) =>
        stopWords.has(term) ? null : term.toLowerCase(), // index term processing
      searchOptions: {
        processTerm: (term) => term.toLowerCase() // search query processing
      }
    })
    
    // To get the default term processor:
    // const defaultProcessor = MiniSearch.getDefault('processTerm')
  8. Customize tokenization

    master

    Control how strings are split into tokens using the tokenize option. You can set a tokenizer for indexing and a different one for searching via searchOptions.tokenize.

    // Custom tokenizer for both indexing and searching
    let miniSearch = new MiniSearch({
      fields: ['title', 'text'],
      tokenize: (string, _fieldName) => string.split('-')
    })
    
    // Different tokenizers for indexing vs searching
    let miniSearch = new MiniSearch({
      fields: ['title', 'text'],
      tokenize: (string) => string.split('-'), // indexing tokenizer
      searchOptions: {
        tokenize: (string) => string.split(/[\s-]+/) // search query tokenizer
      }
    })
    
    // To get the default tokenizer:
    // const defaultTokenizer = MiniSearch.getDefault('tokenize')
  9. Customize field extraction

    master

    By default, MiniSearch treats documents as plain key-value objects. Use the extractField option to implement custom logic for nested fields or non-string values. The function receives (document, fieldName) and should return the value to be indexed.

    let miniSearch = new MiniSearch({
      fields: ['title', 'author.name', 'pubYear'],
      extractField: (document, fieldName) => {
        // Extract year from a Date object
        if (fieldName === 'pubYear') {
          const pubDate = document['pubDate']
          return pubDate && pubDate.getFullYear().toString()
        }
    
        // Access nested fields (e.g., 'author.name')
        return fieldName.split('.').reduce((doc, key) => doc && doc[key], document)
      }
    })
    
    // To get the default extractor:
    // const defaultExtractor = MiniSearch.getDefault('extractField')
  10. Configure MiniSearch instance options

    master

    When initializing a MiniSearch instance, you can provide several configuration options to define how documents are processed and stored:

    • idField: The field used as the document ID. Defaults to 'id'.
    • extractField: A function (document: any, fieldName: string) => any to define how values are retrieved from a document.
    • stringifyField: A function (fieldValue: any, fieldName: string) => string to convert field values to strings for indexing. Defaults to .toString().
    • tokenize: A function (text: string) => string[] to split text into tokens. Defaults to splitting by Unicode space or punctuation.
    • processTerm: A function (term: string) => string to transform search terms (e.g., lowercase). Defaults to .toLowerCase().
    • fields: The fields to be indexed.
    • searchOptions: Default search options to be used for all queries.
    • storeFields: An array of field names whose values should be stored in the index for retrieval.
    • autoVacuum: Boolean to enable/disable automatic index cleanup. Defaults to true.
  11. Configure AutoVacuum behavior

    master

    Vacuuming cleans up obsolete references left by discard(). You can control this via the autoVacuum option in the constructor.

    AutoVacuumOptions includes:

    • batchSize: Number of terms to traverse per batch (default: 1000).
    • batchWait: Milliseconds to wait between batches (default: 10).
    • minDirtCount: Minimum number of discarded documents required to trigger vacuum (default: 20).
    • minDirtFactor: Minimum proportion of discarded documents required to trigger vacuum (default: 0.1).