forceCalendar
Core Package

Search

Full-text search with fuzzy matching, Web Worker offloading, and inverted index.

Overview

forceCalendar provides two search implementations:

ClassExecutionBest For
EventSearchMain threadSmall datasets (under 1000 events), synchronous results
SearchWorkerManagerWeb WorkerLarge datasets, non-blocking UI

Both support fuzzy matching via Levenshtein distance and field-weighted scoring.

EventSearch

Full-text search with fuzzy matching, filtering, grouping, and suggestions.

import { EventSearch } from '@forcecalendar/core';

const search = new EventSearch(eventStore);

search(query, options?)

Search events by text query.

const results = search.search('standup', {
  fields: ['title', 'description', 'location'],
  fuzzy: true,
  caseSensitive: false,
  limit: 20,
  sortBy: 'relevance', // or 'start', 'title'
});
// Returns array of Event objects sorted by relevance
OptionTypeDefaultDescription
fieldsstring[]['title', 'description', 'location']Fields to search
fuzzybooleanfalseEnable Levenshtein distance matching
caseSensitivebooleanfalseCase-sensitive matching
limitnumber50Maximum results
sortBystring'relevance'Sort order

Fuzzy matching uses Levenshtein distance with a threshold proportional to query length. Short queries (1-3 chars) require exact prefix matches; longer queries allow distance up to Math.floor(queryLength / 3).

filter(filters)

Filter events by structured criteria without a text query.

const results = search.filter({
  dateRange: {
    start: new Date('2026-03-01'),
    end: new Date('2026-03-31'),
  },
  categories: ['meeting', 'review'],
  locations: ['Room 301', 'Room 302'],
  attendees: ['alice@example.com'],
  status: ['confirmed', 'tentative'],
  allDay: false,
  recurring: true,
  hasReminders: true,
  custom: (event) => event.durationMinutes > 30,
});
FilterTypeDescription
dateRange{ start, end }Events within date range
categoriesstring[]Events matching any category
locationsstring[]Events at any of these locations
attendeesstring[]Events with any of these attendees
statusstring[]Events with any of these statuses
allDaybooleanAll-day event filter
recurringbooleanRecurring event filter
hasRemindersbooleanHas reminders filter
customFunctionCustom predicate function

advancedSearch(query, filters?, options?)

Combine text search with structured filters.

const results = search.advancedSearch(
  'planning',
  {
    categories: ['meeting'],
    dateRange: { start: new Date('2026-03-01'), end: new Date('2026-03-31') },
  },
  { fuzzy: true, limit: 10 }
);

getSuggestions(partial, options?)

Get autocomplete suggestions based on partial input.

const suggestions = search.getSuggestions('spr', {
  field: 'title',  // Search in specific field
  limit: 5,
});
// ['Sprint Planning', 'Spring Review']

getUniqueValues(field)

Get all unique values for a given field across all events.

const locations = search.getUniqueValues('location');
// ['Room 301', 'Room 302', 'Virtual']

groupBy(field, options?)

Group events by a field value.

const grouped = search.groupBy('category');
// { meeting: [Event, ...], review: [Event, ...] }

rebuildIndex()

Force a rebuild of the search index. Called automatically when events change.

SearchWorkerManager

Offloads search to a Web Worker for non-blocking performance on large datasets. Falls back to InvertedIndex on the main thread if Web Workers are unavailable (e.g., Salesforce Locker Service).

import { SearchWorkerManager } from '@forcecalendar/core';

const manager = new SearchWorkerManager(eventStore);

search(query, options?)

Asynchronous search. Returns a Promise.

const results = await manager.search('standup', {
  fields: ['title', 'description', 'location'],
  fuzzy: true,
  limit: 20,
});

indexEvents()

Rebuild the search index in the worker.

await manager.indexEvents();

clear()

Clear the worker's search index.

destroy()

Terminate the Web Worker and clean up.

InvertedIndex

The fallback search implementation used when Web Workers are unavailable. Also exported for direct use.

import { InvertedIndex } from '@forcecalendar/core';

const index = new InvertedIndex();

// Build index from events
index.buildIndex(events);

// Search
const results = index.search('standup', {
  fields: ['title', 'description', 'location'],
  fuzzy: true,
});

Field Boost Weights

The inverted index applies these weights when scoring search results:

FieldBoost
title2.0
location1.5
category1.5
description1.0

Tokenization

Text is tokenized by:

  1. Converting to lowercase
  2. Splitting on whitespace and punctuation
  3. Filtering tokens shorter than 2 characters

Methods

MethodDescription
buildIndex(events)Build the inverted index from events
tokenize(text)Tokenize a string
search(query, options)Search the index
clear()Clear the index