smartfuzzy/ts/smartfuzzy.classes.objectsorter.ts

114 lines
3.1 KiB
TypeScript

import * as plugins from './smartfuzzy.plugins.js';
/**
* Result of a fuzzy search on objects
*
* @typeParam T - The type of object being searched
*/
export interface IFuzzySearchResult<T> {
/** The matched object */
item: T;
/** The index of the object in the original array */
refIndex: number;
/** The match score (lower is better) */
score?: number;
}
/**
* Handles fuzzy searching and sorting of objects based on their properties
*
* @typeParam T - The type of objects to search through
*
* @example
* ```typescript
* interface User {
* name: string;
* email: string;
* }
*
* const users = [
* { name: 'John Smith', email: 'john@example.com' },
* { name: 'Jane Doe', email: 'jane@example.com' }
* ];
*
* const sorter = new ObjectSorter<User>(users);
* const results = sorter.sort('john', ['name', 'email']);
* ```
*/
export class ObjectSorter<T> {
/**
* The collection of objects to search through
*/
public objectDictionary: T[];
/**
* Creates a new ObjectSorter instance
*
* @param objectDictionaryArg - Array of objects to search through
*/
constructor(objectDictionaryArg: T[] = []) {
if (objectDictionaryArg !== undefined && !Array.isArray(objectDictionaryArg)) {
throw new Error('Object dictionary must be an array');
}
this.objectDictionary = objectDictionaryArg;
}
/**
* Searches and sorts objects based on how well they match the search string
* in the specified object properties
*
* @param stringArg - The search query string
* @param objectKeysArg - Array of object property names to search within
* @returns Array of results sorted by relevance (best matches first)
*
* @example
* ```typescript
* // Search for 'john' in both name and email fields
* const results = sorter.sort('john', ['name', 'email']);
*
* // First result is the best match
* console.log(results[0].item.name); // 'John Smith'
* ```
*/
public sort(stringArg: string, objectKeysArg: string[]): Array<IFuzzySearchResult<T>> {
if (typeof stringArg !== 'string') {
throw new Error('Search string must be a string');
}
if (!Array.isArray(objectKeysArg)) {
throw new Error('Object keys must be an array');
}
if (objectKeysArg.length === 0) {
throw new Error('At least one object key must be provided for searching');
}
// Verify all keys are strings
for (const key of objectKeysArg) {
if (typeof key !== 'string') {
throw new Error('All object keys must be strings');
}
}
// Empty dictionary should return empty results instead of error
if (this.objectDictionary.length === 0) {
return [];
}
const fuseOptions = {
shouldSort: true,
threshold: 0.6, // Lower values = more strict matching
location: 0, // Where to start searching in the string
distance: 100, // How far to search in the string
maxPatternLength: 32,
minMatchCharLength: 1,
keys: objectKeysArg,
};
const fuse = new plugins.fuseJs<T>(this.objectDictionary, fuseOptions);
const result = fuse.search(stringArg);
return result;
}
}