fix(core): Update build scripts, refine testing assertions, and enhance documentation
This commit is contained in:
@@ -1,37 +1,177 @@
|
||||
import * as plugins from './smartfuzzy.plugins.js';
|
||||
|
||||
/**
|
||||
* an article search that searches articles in a weighted manner
|
||||
* Type for the search result returned by ArticleSearch
|
||||
*/
|
||||
export type IArticleSearchResult = {
|
||||
/** The matched article */
|
||||
item: plugins.tsclass.content.IArticle;
|
||||
|
||||
/** The index of the article in the original array */
|
||||
refIndex: number;
|
||||
|
||||
/** The match score (lower is better) */
|
||||
score?: number;
|
||||
|
||||
/** Information about where matches were found in the article */
|
||||
matches?: ReadonlyArray<{
|
||||
indices: ReadonlyArray<readonly [number, number]>;
|
||||
key?: string;
|
||||
value?: string;
|
||||
refIndex?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized search engine for articles with weighted field searching
|
||||
*
|
||||
* This class provides fuzzy searching against article content, with different weights
|
||||
* assigned to different parts of the article (title, tags, content) to provide
|
||||
* more relevant results.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const articles = [
|
||||
* {
|
||||
* title: 'Getting Started with TypeScript',
|
||||
* content: 'TypeScript is a superset of JavaScript that adds static typing...',
|
||||
* tags: ['typescript', 'javascript', 'programming'],
|
||||
* author: 'John Doe',
|
||||
* timestamp: Date.now(),
|
||||
* featuredImageUrl: null,
|
||||
* url: 'https://example.com/typescript-intro'
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
* const articleSearch = new ArticleSearch(articles);
|
||||
* const results = await articleSearch.search('typescript');
|
||||
* ```
|
||||
*/
|
||||
export class ArticleSearch {
|
||||
/**
|
||||
* Collection of articles to search through
|
||||
*/
|
||||
public articles: plugins.tsclass.content.IArticle[] = [];
|
||||
|
||||
/**
|
||||
* Flag indicating whether the search index needs to be updated
|
||||
*/
|
||||
public needsUpdate: boolean = false;
|
||||
|
||||
/**
|
||||
* Promise manager for async operations
|
||||
*/
|
||||
private readyDeferred = plugins.smartpromise.defer();
|
||||
|
||||
/**
|
||||
* Fuse.js instance for searching
|
||||
*/
|
||||
private fuse: plugins.fuseJs<plugins.tsclass.content.IArticle>;
|
||||
|
||||
/**
|
||||
* Creates a new ArticleSearch instance
|
||||
*
|
||||
* @param articleArrayArg - Optional array of articles to initialize with
|
||||
*/
|
||||
constructor(articleArrayArg?: plugins.tsclass.content.IArticle[]) {
|
||||
// Validate input if provided
|
||||
if (articleArrayArg !== undefined && !Array.isArray(articleArrayArg)) {
|
||||
throw new Error('Article array must be an array');
|
||||
}
|
||||
|
||||
this.fuse = new plugins.fuseJs(this.articles);
|
||||
this.readyDeferred.resolve();
|
||||
|
||||
if (articleArrayArg) {
|
||||
for (const article of articleArrayArg) {
|
||||
// Validate each article has required fields
|
||||
if (!article || typeof article !== 'object') {
|
||||
throw new Error('Each article must be a valid object');
|
||||
}
|
||||
|
||||
// Require at least title field
|
||||
if (!article.title || typeof article.title !== 'string') {
|
||||
throw new Error('Each article must have a title string');
|
||||
}
|
||||
|
||||
this.addArticle(article);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* allows adding an article
|
||||
* Adds an article to the collection and marks the index for updating
|
||||
*
|
||||
* @param articleArg - The article to add to the search collection
|
||||
* @returns void
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* articleSearch.addArticle({
|
||||
* title: 'Advanced TypeScript Features',
|
||||
* content: 'This article covers advanced TypeScript concepts...',
|
||||
* tags: ['typescript', 'advanced'],
|
||||
* author: 'Jane Smith',
|
||||
* timestamp: Date.now(),
|
||||
* featuredImageUrl: null,
|
||||
* url: 'https://example.com/advanced-typescript'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
addArticle(articleArg: plugins.tsclass.content.IArticle) {
|
||||
public addArticle(articleArg: plugins.tsclass.content.IArticle): void {
|
||||
if (!articleArg || typeof articleArg !== 'object') {
|
||||
throw new Error('Article must be a valid object');
|
||||
}
|
||||
|
||||
// Require at least title field
|
||||
if (!articleArg.title || typeof articleArg.title !== 'string') {
|
||||
throw new Error('Article must have a title string');
|
||||
}
|
||||
|
||||
// Validate tags if present
|
||||
if (articleArg.tags !== undefined && !Array.isArray(articleArg.tags)) {
|
||||
throw new Error('Article tags must be an array of strings');
|
||||
}
|
||||
|
||||
this.articles.push(articleArg);
|
||||
this.needsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* allows searching an article
|
||||
* Performs a weighted fuzzy search across all articles
|
||||
*
|
||||
* The search uses the following weighting:
|
||||
* - Title: 3x importance
|
||||
* - Tags: 2x importance
|
||||
* - Content: 1x importance
|
||||
*
|
||||
* @param searchStringArg - The search query string
|
||||
* @returns Array of articles matched with their relevance score and match details
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Search for articles about TypeScript
|
||||
* const results = await articleSearch.search('typescript');
|
||||
*
|
||||
* // Access the first (most relevant) result
|
||||
* if (results.length > 0) {
|
||||
* console.log(results[0].item.title);
|
||||
*
|
||||
* // See where the match was found
|
||||
* console.log(results[0].matches);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public async search(searchStringArg: string) {
|
||||
public async search(searchStringArg: string): Promise<IArticleSearchResult[]> {
|
||||
if (typeof searchStringArg !== 'string') {
|
||||
throw new Error('Search string must be a string');
|
||||
}
|
||||
|
||||
// Empty article collection should return empty results
|
||||
if (this.articles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.needsUpdate) {
|
||||
const oldDeferred = this.readyDeferred;
|
||||
this.readyDeferred = plugins.smartpromise.defer();
|
||||
|
Reference in New Issue
Block a user