spotr 1.0.0-alpha.3 → 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Spotr.cjs +1 -1
- package/dist/Spotr.cjs.map +1 -1
- package/dist/Spotr.js +68 -60
- package/dist/Spotr.js.map +1 -1
- package/dist/bundle-size.json +2 -2
- package/dist/fuzzy/levenshtein.cjs +1 -1
- package/dist/fuzzy/levenshtein.cjs.map +1 -1
- package/dist/fuzzy/levenshtein.js +27 -39
- package/dist/fuzzy/levenshtein.js.map +1 -1
- package/dist/fuzzy/scorer.cjs +1 -1
- package/dist/fuzzy/scorer.cjs.map +1 -1
- package/dist/fuzzy/scorer.js +31 -29
- package/dist/fuzzy/scorer.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +1 -1
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.js +9 -19
- package/dist/preact.js.map +1 -1
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +9 -19
- package/dist/react.js.map +1 -1
- package/dist/solid.cjs +1 -1
- package/dist/solid.cjs.map +1 -1
- package/dist/solid.d.ts +5 -11
- package/dist/solid.js +9 -7
- package/dist/solid.js.map +1 -1
- package/dist/svelte.cjs +1 -1
- package/dist/svelte.cjs.map +1 -1
- package/dist/svelte.d.ts +5 -12
- package/dist/svelte.js +9 -7
- package/dist/svelte.js.map +1 -1
- package/dist/utils/shallowEqual.cjs +2 -0
- package/dist/utils/shallowEqual.cjs.map +1 -0
- package/dist/utils/shallowEqual.js +15 -0
- package/dist/utils/shallowEqual.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Spotr
|
|
4
4
|
|
|
5
|
-
A powerful fuzzy search library for client-side collections.
|
|
5
|
+
A powerful fuzzy search library for client-side collections in TypeScript.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/spotr) [](https://bundlephobia.com/package/spotr) [](https://github.com/andymerskin/spotr/actions/workflows/ci.yml)
|
|
8
8
|
|
package/dist/Spotr.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var k=Object.defineProperty;var S=(u,e,t)=>e in u?k(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var o=(u,e,t)=>S(u,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("./utils/validate.cjs"),y=require("./fuzzy/scorer.cjs"),T=require("./utils/tokenize.cjs");class L{constructor(e){o(this,"_collection");o(this,"_fields");o(this,"_keywords");o(this,"_keywordTriggerMap");o(this,"_threshold");o(this,"_limit");o(this,"_debounce");o(this,"_caseSensitive");o(this,"_minMatchCharLength");o(this,"_maxStringLength");o(this,"_debounceTimer",null);o(this,"_optionsSnapshot");this._optionsSnapshot=e;const t=f.validateOptions(e);this._collection=t.collection,this._threshold=t.threshold,this._limit=t.limit,this._debounce=t.debounce,this._caseSensitive=t.caseSensitive,this._minMatchCharLength=t.minMatchCharLength,this._maxStringLength=t.maxStringLength,this._fields=y.normalizeFieldConfig(e.fields,this._threshold),this._keywords=e.keywords?f.validateKeywords(e.keywords):null,this._keywordTriggerMap=this._buildKeywordTriggerMap()}_buildKeywordTriggerMap(){const e=new Map;if(!this._keywords)return e;for(const t of this._keywords.definitions){const c=Array.isArray(t.triggers)?t.triggers:[t.triggers];for(const n of c)e.set(n.toLowerCase(),t)}return e}get collection(){return this._collection}get options(){return this._optionsSnapshot}setCollection(e){this._collection=f.validateCollection(e)}query(e){const t=T.tokenize(e);if(t.length===0)return{results:[],matchedKeywords:[],tokens:[],warnings:[]};const{keywordTokens:c,searchTokens:n}=this._extractKeywords(t);let h=this._collection;const s=[];if(c.size>0){const r=this._applyKeywords(h,c);h=r.collection,s.push(...r.matchedKeywords)}if(n.length===0){const r=[];return{results:h.map(_=>{const{score:w,warnings:p}=y.scoreItem(_,[],this._fields,this._caseSensitive,this._maxStringLength);return r.push(...p),{item:_,score:w}}),matchedKeywords:s,tokens:n,warnings:[...new Set(r)]}}const i=this._minMatchCharLength>1?n.filter(r=>r.length>=this._minMatchCharLength):n;if(i.length===0)return{results:[],matchedKeywords:s,tokens:n,warnings:[]};const l=[],d=[];for(const r of i){let a=r;a.length>this._maxStringLength&&(l.push(`Query string truncated from ${a.length} to ${this._maxStringLength} characters for performance`),a=a.slice(0,this._maxStringLength)),d.push(this._caseSensitive?a:a.toLowerCase())}const g=[];for(const r of h){const{score:a,warnings:_}=y.scoreItem(r,d,this._fields,this._caseSensitive,this._maxStringLength);l.push(..._),a>0&&g.push({item:r,score:a})}return g.sort((r,a)=>a.score-r.score),{results:this._limit<1/0?g.slice(0,this._limit):g,matchedKeywords:s,tokens:n,warnings:[...new Set(l)]}}queryAsync(e){return new Promise(t=>{this._debounce>0?(this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(()=>{t(this.query(e))},this._debounce)):t(this.query(e))})}_extractKeywords(e){const t=new Map,c=[];for(const n of e){const h=this._caseSensitive?n:n.toLowerCase(),s=this._keywordTriggerMap.get(h);if(s){const i=t.get(s.name)||[];i.push(n),t.set(s.name,i)}else c.push(n)}return{keywordTokens:t,searchTokens:c}}_applyKeywords(e,t){if(!this._keywords)return{collection:e,matchedKeywords:[]};let c=e;const n=[],h=new Map;for(const[s,i]of t)h.set(s,i);if(this._keywords.mode==="and")for(const s of this._keywords.definitions){const i=h.get(s.name);if(i){const l=s.handler,d=l(c,i);if(!Array.isArray(d)){console.error(`[Spotr] Keyword handler "${s.name}" must return an array, received ${typeof d}. Skipping this filter.`);continue}c=d,n.push({name:s.name,terms:i})}}else{const s=[],i=new Set;for(const l of this._keywords.definitions){const d=h.get(l.name);if(d){const g=l.handler,m=g(this._collection,d);if(!Array.isArray(m)){console.error(`[Spotr] Keyword handler "${l.name}" must return an array, received ${typeof m}. Skipping this filter.`);continue}n.push({name:l.name,terms:d});for(const r of m)i.has(r)||(i.add(r),s.push(r))}}c=s}return{collection:c,matchedKeywords:n}}}exports.Spotr=L;
|
|
2
2
|
//# sourceMappingURL=Spotr.cjs.map
|
package/dist/Spotr.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spotr.cjs","sources":["../src/Spotr.ts"],"sourcesContent":["import { normalizeFieldConfig, scoreItem } from './fuzzy';\nimport {\n tokenize,\n validateOptions,\n validateKeywords,\n validateCollection,\n} from './utils';\nimport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n NormalizedFieldConfig,\n NormalizedKeywordsConfig,\n KeywordDefinition,\n} from './types';\n\nexport class Spotr<T extends object> {\n private _collection: T[];\n private _fields: NormalizedFieldConfig[];\n private _keywords: NormalizedKeywordsConfig | null;\n private _keywordTriggerMap: Map<string, KeywordDefinition<unknown>>;\n private _threshold: number;\n private _limit: number;\n private _debounce: number;\n private _caseSensitive: boolean;\n private _minMatchCharLength: number;\n private _maxStringLength: number;\n private _debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private _optionsSnapshot: SpotrOptions<T>;\n\n constructor(options: SpotrOptions<T>) {\n this._optionsSnapshot = options;\n const validated = validateOptions(options);\n this._collection = validated.collection;\n this._threshold = validated.threshold;\n this._limit = validated.limit;\n this._debounce = validated.debounce;\n this._caseSensitive = validated.caseSensitive;\n this._minMatchCharLength = validated.minMatchCharLength;\n this._maxStringLength = validated.maxStringLength;\n\n this._fields = normalizeFieldConfig(options.fields, this._threshold);\n\n this._keywords = options.keywords\n ? validateKeywords(options.keywords)\n : null;\n this._keywordTriggerMap = this._buildKeywordTriggerMap();\n }\n\n private _buildKeywordTriggerMap(): Map<string, KeywordDefinition<unknown>> {\n const map = new Map<string, KeywordDefinition<unknown>>();\n if (!this._keywords) return map;\n\n for (const def of this._keywords.definitions) {\n const triggers = Array.isArray(def.triggers)\n ? def.triggers\n : [def.triggers];\n for (const trigger of triggers) {\n map.set(trigger.toLowerCase(), def);\n }\n }\n return map;\n }\n\n get collection(): T[] {\n return this._collection;\n }\n\n get options(): SpotrOptions<T> {\n return this._optionsSnapshot;\n }\n\n setCollection(collection: T[] | Set<T>): void {\n this._collection = validateCollection(collection as unknown) as T[];\n }\n\n query(search: string): SpotrResult<T> {\n const tokens = tokenize(search);\n\n if (tokens.length === 0) {\n return {\n results: [],\n matchedKeywords: [],\n tokens: [],\n warnings: [],\n };\n }\n\n const { keywordTokens, searchTokens } = this._extractKeywords(tokens);\n\n let filteredCollection = this._collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n if (keywordTokens.size > 0) {\n const result = this._applyKeywords(filteredCollection, keywordTokens);\n filteredCollection = result.collection;\n matchedKeywords.push(...result.matchedKeywords);\n }\n\n if (searchTokens.length === 0) {\n const allWarnings: string[] = [];\n const results: ScoredResult<T>[] = filteredCollection.map((item) => {\n const { score, warnings } = scoreItem(\n item,\n [],\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n return { item, score };\n });\n\n return {\n results,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n const validTokens =\n this._minMatchCharLength > 1\n ? searchTokens.filter((t) => t.length >= this._minMatchCharLength)\n : searchTokens;\n\n if (validTokens.length === 0) {\n return {\n results: [],\n matchedKeywords,\n tokens: searchTokens,\n warnings: [],\n };\n }\n\n const allWarnings: string[] = [];\n const scoredResults: ScoredResult<T>[] = [];\n\n for (const item of filteredCollection) {\n const { score, warnings } = scoreItem(\n item,\n validTokens,\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n\n if (score > 0) {\n scoredResults.push({ item, score });\n }\n }\n\n scoredResults.sort((a, b) => b.score - a.score);\n\n const limitedResults =\n this._limit < Infinity\n ? scoredResults.slice(0, this._limit)\n : scoredResults;\n\n return {\n results: limitedResults,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n queryAsync(search: string): Promise<SpotrResult<T>> {\n return new Promise((resolve) => {\n if (this._debounce > 0) {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer);\n }\n this._debounceTimer = setTimeout(() => {\n resolve(this.query(search));\n }, this._debounce);\n } else {\n resolve(this.query(search));\n }\n });\n }\n\n private _extractKeywords(tokens: string[]): {\n keywordTokens: Map<string, string[]>;\n searchTokens: string[];\n } {\n const keywordTokens = new Map<string, string[]>();\n const searchTokens: string[] = [];\n\n for (const token of tokens) {\n const normalizedToken = this._caseSensitive ? token : token.toLowerCase();\n const keywordDef = this._keywordTriggerMap.get(normalizedToken);\n\n if (keywordDef) {\n const existing = keywordTokens.get(keywordDef.name) || [];\n existing.push(token);\n keywordTokens.set(keywordDef.name, existing);\n } else {\n searchTokens.push(token);\n }\n }\n\n return { keywordTokens, searchTokens };\n }\n\n private _applyKeywords(\n collection: T[],\n keywordTokens: Map<string, string[]>\n ): { collection: T[]; matchedKeywords: MatchedKeyword[] } {\n if (!this._keywords) {\n return { collection, matchedKeywords: [] };\n }\n\n let result = collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n const keywordNameToTerms = new Map<string, string[]>();\n for (const [name, terms] of keywordTokens) {\n keywordNameToTerms.set(name, terms);\n }\n\n if (this._keywords.mode === 'and') {\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const handlerResult = handler(result, terms);\n\n if (!Array.isArray(handlerResult)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof handlerResult}. Skipping this filter.`\n );\n // Skip this keyword - keep current collection for this step\n continue;\n }\n\n result = handlerResult;\n matchedKeywords.push({ name: def.name, terms });\n }\n }\n } else {\n const mergedResults: T[] = [];\n const processedItems = new Set<T>();\n\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const keywordResults = handler(this._collection, terms);\n\n if (!Array.isArray(keywordResults)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof keywordResults}. Skipping this filter.`\n );\n // Skip this keyword - don't add its results\n continue;\n }\n\n matchedKeywords.push({ name: def.name, terms });\n\n for (const item of keywordResults) {\n if (!processedItems.has(item)) {\n processedItems.add(item);\n mergedResults.push(item);\n }\n }\n }\n }\n result = mergedResults;\n }\n\n return { collection: result, matchedKeywords };\n }\n}\n"],"names":["Spotr","options","__publicField","validated","validateOptions","normalizeFieldConfig","validateKeywords","map","def","triggers","trigger","collection","validateCollection","search","tokens","tokenize","keywordTokens","searchTokens","filteredCollection","matchedKeywords","result","allWarnings","item","score","warnings","scoreItem","validTokens","t","scoredResults","a","b","resolve","token","normalizedToken","keywordDef","existing","keywordNameToTerms","name","terms","handler","handlerResult","mergedResults","processedItems","keywordResults"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Spotr.cjs","sources":["../src/Spotr.ts"],"sourcesContent":["import { normalizeFieldConfig, scoreItem } from './fuzzy';\nimport {\n tokenize,\n validateOptions,\n validateKeywords,\n validateCollection,\n} from './utils';\nimport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n NormalizedFieldConfig,\n NormalizedKeywordsConfig,\n KeywordDefinition,\n} from './types';\n\nexport class Spotr<T extends object> {\n private _collection: T[];\n private _fields: NormalizedFieldConfig[];\n private _keywords: NormalizedKeywordsConfig | null;\n private _keywordTriggerMap: Map<string, KeywordDefinition<unknown>>;\n private _threshold: number;\n private _limit: number;\n private _debounce: number;\n private _caseSensitive: boolean;\n private _minMatchCharLength: number;\n private _maxStringLength: number;\n private _debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private _optionsSnapshot: SpotrOptions<T>;\n\n constructor(options: SpotrOptions<T>) {\n this._optionsSnapshot = options;\n const validated = validateOptions(options);\n this._collection = validated.collection;\n this._threshold = validated.threshold;\n this._limit = validated.limit;\n this._debounce = validated.debounce;\n this._caseSensitive = validated.caseSensitive;\n this._minMatchCharLength = validated.minMatchCharLength;\n this._maxStringLength = validated.maxStringLength;\n\n this._fields = normalizeFieldConfig(options.fields, this._threshold);\n\n this._keywords = options.keywords\n ? validateKeywords(options.keywords)\n : null;\n this._keywordTriggerMap = this._buildKeywordTriggerMap();\n }\n\n private _buildKeywordTriggerMap(): Map<string, KeywordDefinition<unknown>> {\n const map = new Map<string, KeywordDefinition<unknown>>();\n if (!this._keywords) return map;\n\n for (const def of this._keywords.definitions) {\n const triggers = Array.isArray(def.triggers)\n ? def.triggers\n : [def.triggers];\n for (const trigger of triggers) {\n map.set(trigger.toLowerCase(), def);\n }\n }\n return map;\n }\n\n get collection(): T[] {\n return this._collection;\n }\n\n get options(): SpotrOptions<T> {\n return this._optionsSnapshot;\n }\n\n setCollection(collection: T[] | Set<T>): void {\n this._collection = validateCollection(collection as unknown) as T[];\n }\n\n query(search: string): SpotrResult<T> {\n const tokens = tokenize(search);\n\n if (tokens.length === 0) {\n return {\n results: [],\n matchedKeywords: [],\n tokens: [],\n warnings: [],\n };\n }\n\n const { keywordTokens, searchTokens } = this._extractKeywords(tokens);\n\n let filteredCollection = this._collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n if (keywordTokens.size > 0) {\n const result = this._applyKeywords(filteredCollection, keywordTokens);\n filteredCollection = result.collection;\n matchedKeywords.push(...result.matchedKeywords);\n }\n\n if (searchTokens.length === 0) {\n const allWarnings: string[] = [];\n const results: ScoredResult<T>[] = filteredCollection.map((item) => {\n const { score, warnings } = scoreItem(\n item,\n [], // no search tokens to normalize\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n return { item, score };\n });\n\n return {\n results,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n const validTokens =\n this._minMatchCharLength > 1\n ? searchTokens.filter((t) => t.length >= this._minMatchCharLength)\n : searchTokens;\n\n if (validTokens.length === 0) {\n return {\n results: [],\n matchedKeywords,\n tokens: searchTokens,\n warnings: [],\n };\n }\n\n // Pre-normalize tokens once (truncate, case) to avoid redundant work in the hot path\n const allWarnings: string[] = [];\n const normalizedTokens: string[] = [];\n for (const token of validTokens) {\n let t = token;\n if (t.length > this._maxStringLength) {\n allWarnings.push(\n `Query string truncated from ${t.length} to ${this._maxStringLength} characters for performance`\n );\n t = t.slice(0, this._maxStringLength);\n }\n normalizedTokens.push(this._caseSensitive ? t : t.toLowerCase());\n }\n\n const scoredResults: ScoredResult<T>[] = [];\n\n for (const item of filteredCollection) {\n const { score, warnings } = scoreItem(\n item,\n normalizedTokens,\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n\n if (score > 0) {\n scoredResults.push({ item, score });\n }\n }\n\n scoredResults.sort((a, b) => b.score - a.score);\n\n const limitedResults =\n this._limit < Infinity\n ? scoredResults.slice(0, this._limit)\n : scoredResults;\n\n return {\n results: limitedResults,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n queryAsync(search: string): Promise<SpotrResult<T>> {\n return new Promise((resolve) => {\n if (this._debounce > 0) {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer);\n }\n this._debounceTimer = setTimeout(() => {\n resolve(this.query(search));\n }, this._debounce);\n } else {\n resolve(this.query(search));\n }\n });\n }\n\n private _extractKeywords(tokens: string[]): {\n keywordTokens: Map<string, string[]>;\n searchTokens: string[];\n } {\n const keywordTokens = new Map<string, string[]>();\n const searchTokens: string[] = [];\n\n for (const token of tokens) {\n const normalizedToken = this._caseSensitive ? token : token.toLowerCase();\n const keywordDef = this._keywordTriggerMap.get(normalizedToken);\n\n if (keywordDef) {\n const existing = keywordTokens.get(keywordDef.name) || [];\n existing.push(token);\n keywordTokens.set(keywordDef.name, existing);\n } else {\n searchTokens.push(token);\n }\n }\n\n return { keywordTokens, searchTokens };\n }\n\n private _applyKeywords(\n collection: T[],\n keywordTokens: Map<string, string[]>\n ): { collection: T[]; matchedKeywords: MatchedKeyword[] } {\n if (!this._keywords) {\n return { collection, matchedKeywords: [] };\n }\n\n let result = collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n const keywordNameToTerms = new Map<string, string[]>();\n for (const [name, terms] of keywordTokens) {\n keywordNameToTerms.set(name, terms);\n }\n\n if (this._keywords.mode === 'and') {\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const handlerResult = handler(result, terms);\n\n if (!Array.isArray(handlerResult)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof handlerResult}. Skipping this filter.`\n );\n // Skip this keyword - keep current collection for this step\n continue;\n }\n\n result = handlerResult;\n matchedKeywords.push({ name: def.name, terms });\n }\n }\n } else {\n const mergedResults: T[] = [];\n const processedItems = new Set<T>();\n\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const keywordResults = handler(this._collection, terms);\n\n if (!Array.isArray(keywordResults)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof keywordResults}. Skipping this filter.`\n );\n // Skip this keyword - don't add its results\n continue;\n }\n\n matchedKeywords.push({ name: def.name, terms });\n\n for (const item of keywordResults) {\n if (!processedItems.has(item)) {\n processedItems.add(item);\n mergedResults.push(item);\n }\n }\n }\n }\n result = mergedResults;\n }\n\n return { collection: result, matchedKeywords };\n }\n}\n"],"names":["Spotr","options","__publicField","validated","validateOptions","normalizeFieldConfig","validateKeywords","map","def","triggers","trigger","collection","validateCollection","search","tokens","tokenize","keywordTokens","searchTokens","filteredCollection","matchedKeywords","result","allWarnings","item","score","warnings","scoreItem","validTokens","t","normalizedTokens","token","scoredResults","a","b","resolve","normalizedToken","keywordDef","existing","keywordNameToTerms","name","terms","handler","handlerResult","mergedResults","processedItems","keywordResults"],"mappings":"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"}
|
package/dist/Spotr.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var o = (m, e, t) =>
|
|
4
|
-
import { validateOptions as
|
|
5
|
-
import { normalizeFieldConfig as K, scoreItem as
|
|
6
|
-
import { tokenize as
|
|
7
|
-
class
|
|
1
|
+
var p = Object.defineProperty;
|
|
2
|
+
var k = (m, e, t) => e in m ? p(m, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : m[e] = t;
|
|
3
|
+
var o = (m, e, t) => k(m, typeof e != "symbol" ? e + "" : e, t);
|
|
4
|
+
import { validateOptions as S, validateKeywords as T, validateCollection as L } from "./utils/validate.js";
|
|
5
|
+
import { normalizeFieldConfig as K, scoreItem as f } from "./fuzzy/scorer.js";
|
|
6
|
+
import { tokenize as v } from "./utils/tokenize.js";
|
|
7
|
+
class A {
|
|
8
8
|
constructor(e) {
|
|
9
9
|
o(this, "_collection");
|
|
10
10
|
o(this, "_fields");
|
|
@@ -19,16 +19,16 @@ class x {
|
|
|
19
19
|
o(this, "_debounceTimer", null);
|
|
20
20
|
o(this, "_optionsSnapshot");
|
|
21
21
|
this._optionsSnapshot = e;
|
|
22
|
-
const t =
|
|
23
|
-
this._collection = t.collection, this._threshold = t.threshold, this._limit = t.limit, this._debounce = t.debounce, this._caseSensitive = t.caseSensitive, this._minMatchCharLength = t.minMatchCharLength, this._maxStringLength = t.maxStringLength, this._fields = K(e.fields, this._threshold), this._keywords = e.keywords ?
|
|
22
|
+
const t = S(e);
|
|
23
|
+
this._collection = t.collection, this._threshold = t.threshold, this._limit = t.limit, this._debounce = t.debounce, this._caseSensitive = t.caseSensitive, this._minMatchCharLength = t.minMatchCharLength, this._maxStringLength = t.maxStringLength, this._fields = K(e.fields, this._threshold), this._keywords = e.keywords ? T(e.keywords) : null, this._keywordTriggerMap = this._buildKeywordTriggerMap();
|
|
24
24
|
}
|
|
25
25
|
_buildKeywordTriggerMap() {
|
|
26
26
|
const e = /* @__PURE__ */ new Map();
|
|
27
27
|
if (!this._keywords) return e;
|
|
28
28
|
for (const t of this._keywords.definitions) {
|
|
29
|
-
const
|
|
30
|
-
for (const
|
|
31
|
-
e.set(
|
|
29
|
+
const h = Array.isArray(t.triggers) ? t.triggers : [t.triggers];
|
|
30
|
+
for (const n of h)
|
|
31
|
+
e.set(n.toLowerCase(), t);
|
|
32
32
|
}
|
|
33
33
|
return e;
|
|
34
34
|
}
|
|
@@ -39,10 +39,10 @@ class x {
|
|
|
39
39
|
return this._optionsSnapshot;
|
|
40
40
|
}
|
|
41
41
|
setCollection(e) {
|
|
42
|
-
this._collection =
|
|
42
|
+
this._collection = L(e);
|
|
43
43
|
}
|
|
44
44
|
query(e) {
|
|
45
|
-
const t =
|
|
45
|
+
const t = v(e);
|
|
46
46
|
if (t.length === 0)
|
|
47
47
|
return {
|
|
48
48
|
results: [],
|
|
@@ -50,54 +50,62 @@ class x {
|
|
|
50
50
|
tokens: [],
|
|
51
51
|
warnings: []
|
|
52
52
|
};
|
|
53
|
-
const { keywordTokens:
|
|
53
|
+
const { keywordTokens: h, searchTokens: n } = this._extractKeywords(t);
|
|
54
54
|
let a = this._collection;
|
|
55
55
|
const s = [];
|
|
56
|
-
if (
|
|
57
|
-
const
|
|
58
|
-
a =
|
|
56
|
+
if (h.size > 0) {
|
|
57
|
+
const r = this._applyKeywords(a, h);
|
|
58
|
+
a = r.collection, s.push(...r.matchedKeywords);
|
|
59
59
|
}
|
|
60
|
-
if (
|
|
61
|
-
const
|
|
60
|
+
if (n.length === 0) {
|
|
61
|
+
const r = [];
|
|
62
62
|
return {
|
|
63
|
-
results: a.map((
|
|
64
|
-
const { score:
|
|
65
|
-
|
|
63
|
+
results: a.map((_) => {
|
|
64
|
+
const { score: y, warnings: w } = f(
|
|
65
|
+
_,
|
|
66
66
|
[],
|
|
67
|
+
// no search tokens to normalize
|
|
67
68
|
this._fields,
|
|
68
69
|
this._caseSensitive,
|
|
69
70
|
this._maxStringLength
|
|
70
71
|
);
|
|
71
|
-
return
|
|
72
|
+
return r.push(...w), { item: _, score: y };
|
|
72
73
|
}),
|
|
73
74
|
matchedKeywords: s,
|
|
74
|
-
tokens:
|
|
75
|
-
warnings: [...new Set(
|
|
75
|
+
tokens: n,
|
|
76
|
+
warnings: [...new Set(r)]
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
|
-
const i = this._minMatchCharLength > 1 ?
|
|
79
|
+
const i = this._minMatchCharLength > 1 ? n.filter((r) => r.length >= this._minMatchCharLength) : n;
|
|
79
80
|
if (i.length === 0)
|
|
80
81
|
return {
|
|
81
82
|
results: [],
|
|
82
83
|
matchedKeywords: s,
|
|
83
|
-
tokens:
|
|
84
|
+
tokens: n,
|
|
84
85
|
warnings: []
|
|
85
86
|
};
|
|
86
|
-
const l = [],
|
|
87
|
-
for (const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
const l = [], d = [];
|
|
88
|
+
for (const r of i) {
|
|
89
|
+
let c = r;
|
|
90
|
+
c.length > this._maxStringLength && (l.push(
|
|
91
|
+
`Query string truncated from ${c.length} to ${this._maxStringLength} characters for performance`
|
|
92
|
+
), c = c.slice(0, this._maxStringLength)), d.push(this._caseSensitive ? c : c.toLowerCase());
|
|
93
|
+
}
|
|
94
|
+
const g = [];
|
|
95
|
+
for (const r of a) {
|
|
96
|
+
const { score: c, warnings: _ } = f(
|
|
97
|
+
r,
|
|
98
|
+
d,
|
|
91
99
|
this._fields,
|
|
92
100
|
this._caseSensitive,
|
|
93
101
|
this._maxStringLength
|
|
94
102
|
);
|
|
95
|
-
l.push(...
|
|
103
|
+
l.push(..._), c > 0 && g.push({ item: r, score: c });
|
|
96
104
|
}
|
|
97
|
-
return
|
|
98
|
-
results: this._limit < 1 / 0 ?
|
|
105
|
+
return g.sort((r, c) => c.score - r.score), {
|
|
106
|
+
results: this._limit < 1 / 0 ? g.slice(0, this._limit) : g,
|
|
99
107
|
matchedKeywords: s,
|
|
100
|
-
tokens:
|
|
108
|
+
tokens: n,
|
|
101
109
|
warnings: [...new Set(l)]
|
|
102
110
|
};
|
|
103
111
|
}
|
|
@@ -109,61 +117,61 @@ class x {
|
|
|
109
117
|
});
|
|
110
118
|
}
|
|
111
119
|
_extractKeywords(e) {
|
|
112
|
-
const t = /* @__PURE__ */ new Map(),
|
|
113
|
-
for (const
|
|
114
|
-
const a = this._caseSensitive ?
|
|
120
|
+
const t = /* @__PURE__ */ new Map(), h = [];
|
|
121
|
+
for (const n of e) {
|
|
122
|
+
const a = this._caseSensitive ? n : n.toLowerCase(), s = this._keywordTriggerMap.get(a);
|
|
115
123
|
if (s) {
|
|
116
124
|
const i = t.get(s.name) || [];
|
|
117
|
-
i.push(
|
|
125
|
+
i.push(n), t.set(s.name, i);
|
|
118
126
|
} else
|
|
119
|
-
|
|
127
|
+
h.push(n);
|
|
120
128
|
}
|
|
121
|
-
return { keywordTokens: t, searchTokens:
|
|
129
|
+
return { keywordTokens: t, searchTokens: h };
|
|
122
130
|
}
|
|
123
131
|
_applyKeywords(e, t) {
|
|
124
132
|
if (!this._keywords)
|
|
125
133
|
return { collection: e, matchedKeywords: [] };
|
|
126
|
-
let
|
|
127
|
-
const
|
|
134
|
+
let h = e;
|
|
135
|
+
const n = [], a = /* @__PURE__ */ new Map();
|
|
128
136
|
for (const [s, i] of t)
|
|
129
137
|
a.set(s, i);
|
|
130
138
|
if (this._keywords.mode === "and")
|
|
131
139
|
for (const s of this._keywords.definitions) {
|
|
132
140
|
const i = a.get(s.name);
|
|
133
141
|
if (i) {
|
|
134
|
-
const l = s.handler,
|
|
135
|
-
if (!Array.isArray(
|
|
142
|
+
const l = s.handler, d = l(h, i);
|
|
143
|
+
if (!Array.isArray(d)) {
|
|
136
144
|
console.error(
|
|
137
|
-
`[Spotr] Keyword handler "${s.name}" must return an array, received ${typeof
|
|
145
|
+
`[Spotr] Keyword handler "${s.name}" must return an array, received ${typeof d}. Skipping this filter.`
|
|
138
146
|
);
|
|
139
147
|
continue;
|
|
140
148
|
}
|
|
141
|
-
|
|
149
|
+
h = d, n.push({ name: s.name, terms: i });
|
|
142
150
|
}
|
|
143
151
|
}
|
|
144
152
|
else {
|
|
145
153
|
const s = [], i = /* @__PURE__ */ new Set();
|
|
146
154
|
for (const l of this._keywords.definitions) {
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
const g = l.handler,
|
|
150
|
-
if (!Array.isArray(
|
|
155
|
+
const d = a.get(l.name);
|
|
156
|
+
if (d) {
|
|
157
|
+
const g = l.handler, u = g(this._collection, d);
|
|
158
|
+
if (!Array.isArray(u)) {
|
|
151
159
|
console.error(
|
|
152
|
-
`[Spotr] Keyword handler "${l.name}" must return an array, received ${typeof
|
|
160
|
+
`[Spotr] Keyword handler "${l.name}" must return an array, received ${typeof u}. Skipping this filter.`
|
|
153
161
|
);
|
|
154
162
|
continue;
|
|
155
163
|
}
|
|
156
|
-
|
|
157
|
-
for (const
|
|
158
|
-
i.has(
|
|
164
|
+
n.push({ name: l.name, terms: d });
|
|
165
|
+
for (const r of u)
|
|
166
|
+
i.has(r) || (i.add(r), s.push(r));
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
|
-
|
|
169
|
+
h = s;
|
|
162
170
|
}
|
|
163
|
-
return { collection:
|
|
171
|
+
return { collection: h, matchedKeywords: n };
|
|
164
172
|
}
|
|
165
173
|
}
|
|
166
174
|
export {
|
|
167
|
-
|
|
175
|
+
A as Spotr
|
|
168
176
|
};
|
|
169
177
|
//# sourceMappingURL=Spotr.js.map
|
package/dist/Spotr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Spotr.js","sources":["../src/Spotr.ts"],"sourcesContent":["import { normalizeFieldConfig, scoreItem } from './fuzzy';\nimport {\n tokenize,\n validateOptions,\n validateKeywords,\n validateCollection,\n} from './utils';\nimport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n NormalizedFieldConfig,\n NormalizedKeywordsConfig,\n KeywordDefinition,\n} from './types';\n\nexport class Spotr<T extends object> {\n private _collection: T[];\n private _fields: NormalizedFieldConfig[];\n private _keywords: NormalizedKeywordsConfig | null;\n private _keywordTriggerMap: Map<string, KeywordDefinition<unknown>>;\n private _threshold: number;\n private _limit: number;\n private _debounce: number;\n private _caseSensitive: boolean;\n private _minMatchCharLength: number;\n private _maxStringLength: number;\n private _debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private _optionsSnapshot: SpotrOptions<T>;\n\n constructor(options: SpotrOptions<T>) {\n this._optionsSnapshot = options;\n const validated = validateOptions(options);\n this._collection = validated.collection;\n this._threshold = validated.threshold;\n this._limit = validated.limit;\n this._debounce = validated.debounce;\n this._caseSensitive = validated.caseSensitive;\n this._minMatchCharLength = validated.minMatchCharLength;\n this._maxStringLength = validated.maxStringLength;\n\n this._fields = normalizeFieldConfig(options.fields, this._threshold);\n\n this._keywords = options.keywords\n ? validateKeywords(options.keywords)\n : null;\n this._keywordTriggerMap = this._buildKeywordTriggerMap();\n }\n\n private _buildKeywordTriggerMap(): Map<string, KeywordDefinition<unknown>> {\n const map = new Map<string, KeywordDefinition<unknown>>();\n if (!this._keywords) return map;\n\n for (const def of this._keywords.definitions) {\n const triggers = Array.isArray(def.triggers)\n ? def.triggers\n : [def.triggers];\n for (const trigger of triggers) {\n map.set(trigger.toLowerCase(), def);\n }\n }\n return map;\n }\n\n get collection(): T[] {\n return this._collection;\n }\n\n get options(): SpotrOptions<T> {\n return this._optionsSnapshot;\n }\n\n setCollection(collection: T[] | Set<T>): void {\n this._collection = validateCollection(collection as unknown) as T[];\n }\n\n query(search: string): SpotrResult<T> {\n const tokens = tokenize(search);\n\n if (tokens.length === 0) {\n return {\n results: [],\n matchedKeywords: [],\n tokens: [],\n warnings: [],\n };\n }\n\n const { keywordTokens, searchTokens } = this._extractKeywords(tokens);\n\n let filteredCollection = this._collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n if (keywordTokens.size > 0) {\n const result = this._applyKeywords(filteredCollection, keywordTokens);\n filteredCollection = result.collection;\n matchedKeywords.push(...result.matchedKeywords);\n }\n\n if (searchTokens.length === 0) {\n const allWarnings: string[] = [];\n const results: ScoredResult<T>[] = filteredCollection.map((item) => {\n const { score, warnings } = scoreItem(\n item,\n [],\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n return { item, score };\n });\n\n return {\n results,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n const validTokens =\n this._minMatchCharLength > 1\n ? searchTokens.filter((t) => t.length >= this._minMatchCharLength)\n : searchTokens;\n\n if (validTokens.length === 0) {\n return {\n results: [],\n matchedKeywords,\n tokens: searchTokens,\n warnings: [],\n };\n }\n\n const allWarnings: string[] = [];\n const scoredResults: ScoredResult<T>[] = [];\n\n for (const item of filteredCollection) {\n const { score, warnings } = scoreItem(\n item,\n validTokens,\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n\n if (score > 0) {\n scoredResults.push({ item, score });\n }\n }\n\n scoredResults.sort((a, b) => b.score - a.score);\n\n const limitedResults =\n this._limit < Infinity\n ? scoredResults.slice(0, this._limit)\n : scoredResults;\n\n return {\n results: limitedResults,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n queryAsync(search: string): Promise<SpotrResult<T>> {\n return new Promise((resolve) => {\n if (this._debounce > 0) {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer);\n }\n this._debounceTimer = setTimeout(() => {\n resolve(this.query(search));\n }, this._debounce);\n } else {\n resolve(this.query(search));\n }\n });\n }\n\n private _extractKeywords(tokens: string[]): {\n keywordTokens: Map<string, string[]>;\n searchTokens: string[];\n } {\n const keywordTokens = new Map<string, string[]>();\n const searchTokens: string[] = [];\n\n for (const token of tokens) {\n const normalizedToken = this._caseSensitive ? token : token.toLowerCase();\n const keywordDef = this._keywordTriggerMap.get(normalizedToken);\n\n if (keywordDef) {\n const existing = keywordTokens.get(keywordDef.name) || [];\n existing.push(token);\n keywordTokens.set(keywordDef.name, existing);\n } else {\n searchTokens.push(token);\n }\n }\n\n return { keywordTokens, searchTokens };\n }\n\n private _applyKeywords(\n collection: T[],\n keywordTokens: Map<string, string[]>\n ): { collection: T[]; matchedKeywords: MatchedKeyword[] } {\n if (!this._keywords) {\n return { collection, matchedKeywords: [] };\n }\n\n let result = collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n const keywordNameToTerms = new Map<string, string[]>();\n for (const [name, terms] of keywordTokens) {\n keywordNameToTerms.set(name, terms);\n }\n\n if (this._keywords.mode === 'and') {\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const handlerResult = handler(result, terms);\n\n if (!Array.isArray(handlerResult)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof handlerResult}. Skipping this filter.`\n );\n // Skip this keyword - keep current collection for this step\n continue;\n }\n\n result = handlerResult;\n matchedKeywords.push({ name: def.name, terms });\n }\n }\n } else {\n const mergedResults: T[] = [];\n const processedItems = new Set<T>();\n\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const keywordResults = handler(this._collection, terms);\n\n if (!Array.isArray(keywordResults)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof keywordResults}. Skipping this filter.`\n );\n // Skip this keyword - don't add its results\n continue;\n }\n\n matchedKeywords.push({ name: def.name, terms });\n\n for (const item of keywordResults) {\n if (!processedItems.has(item)) {\n processedItems.add(item);\n mergedResults.push(item);\n }\n }\n }\n }\n result = mergedResults;\n }\n\n return { collection: result, matchedKeywords };\n }\n}\n"],"names":["Spotr","options","__publicField","validated","validateOptions","normalizeFieldConfig","validateKeywords","map","def","triggers","trigger","collection","validateCollection","search","tokens","tokenize","keywordTokens","searchTokens","filteredCollection","matchedKeywords","result","allWarnings","item","score","warnings","scoreItem","validTokens","t","scoredResults","a","b","resolve","token","normalizedToken","keywordDef","existing","keywordNameToTerms","name","terms","handler","handlerResult","mergedResults","processedItems","keywordResults"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Spotr.js","sources":["../src/Spotr.ts"],"sourcesContent":["import { normalizeFieldConfig, scoreItem } from './fuzzy';\nimport {\n tokenize,\n validateOptions,\n validateKeywords,\n validateCollection,\n} from './utils';\nimport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n NormalizedFieldConfig,\n NormalizedKeywordsConfig,\n KeywordDefinition,\n} from './types';\n\nexport class Spotr<T extends object> {\n private _collection: T[];\n private _fields: NormalizedFieldConfig[];\n private _keywords: NormalizedKeywordsConfig | null;\n private _keywordTriggerMap: Map<string, KeywordDefinition<unknown>>;\n private _threshold: number;\n private _limit: number;\n private _debounce: number;\n private _caseSensitive: boolean;\n private _minMatchCharLength: number;\n private _maxStringLength: number;\n private _debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private _optionsSnapshot: SpotrOptions<T>;\n\n constructor(options: SpotrOptions<T>) {\n this._optionsSnapshot = options;\n const validated = validateOptions(options);\n this._collection = validated.collection;\n this._threshold = validated.threshold;\n this._limit = validated.limit;\n this._debounce = validated.debounce;\n this._caseSensitive = validated.caseSensitive;\n this._minMatchCharLength = validated.minMatchCharLength;\n this._maxStringLength = validated.maxStringLength;\n\n this._fields = normalizeFieldConfig(options.fields, this._threshold);\n\n this._keywords = options.keywords\n ? validateKeywords(options.keywords)\n : null;\n this._keywordTriggerMap = this._buildKeywordTriggerMap();\n }\n\n private _buildKeywordTriggerMap(): Map<string, KeywordDefinition<unknown>> {\n const map = new Map<string, KeywordDefinition<unknown>>();\n if (!this._keywords) return map;\n\n for (const def of this._keywords.definitions) {\n const triggers = Array.isArray(def.triggers)\n ? def.triggers\n : [def.triggers];\n for (const trigger of triggers) {\n map.set(trigger.toLowerCase(), def);\n }\n }\n return map;\n }\n\n get collection(): T[] {\n return this._collection;\n }\n\n get options(): SpotrOptions<T> {\n return this._optionsSnapshot;\n }\n\n setCollection(collection: T[] | Set<T>): void {\n this._collection = validateCollection(collection as unknown) as T[];\n }\n\n query(search: string): SpotrResult<T> {\n const tokens = tokenize(search);\n\n if (tokens.length === 0) {\n return {\n results: [],\n matchedKeywords: [],\n tokens: [],\n warnings: [],\n };\n }\n\n const { keywordTokens, searchTokens } = this._extractKeywords(tokens);\n\n let filteredCollection = this._collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n if (keywordTokens.size > 0) {\n const result = this._applyKeywords(filteredCollection, keywordTokens);\n filteredCollection = result.collection;\n matchedKeywords.push(...result.matchedKeywords);\n }\n\n if (searchTokens.length === 0) {\n const allWarnings: string[] = [];\n const results: ScoredResult<T>[] = filteredCollection.map((item) => {\n const { score, warnings } = scoreItem(\n item,\n [], // no search tokens to normalize\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n return { item, score };\n });\n\n return {\n results,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n const validTokens =\n this._minMatchCharLength > 1\n ? searchTokens.filter((t) => t.length >= this._minMatchCharLength)\n : searchTokens;\n\n if (validTokens.length === 0) {\n return {\n results: [],\n matchedKeywords,\n tokens: searchTokens,\n warnings: [],\n };\n }\n\n // Pre-normalize tokens once (truncate, case) to avoid redundant work in the hot path\n const allWarnings: string[] = [];\n const normalizedTokens: string[] = [];\n for (const token of validTokens) {\n let t = token;\n if (t.length > this._maxStringLength) {\n allWarnings.push(\n `Query string truncated from ${t.length} to ${this._maxStringLength} characters for performance`\n );\n t = t.slice(0, this._maxStringLength);\n }\n normalizedTokens.push(this._caseSensitive ? t : t.toLowerCase());\n }\n\n const scoredResults: ScoredResult<T>[] = [];\n\n for (const item of filteredCollection) {\n const { score, warnings } = scoreItem(\n item,\n normalizedTokens,\n this._fields,\n this._caseSensitive,\n this._maxStringLength\n );\n allWarnings.push(...warnings);\n\n if (score > 0) {\n scoredResults.push({ item, score });\n }\n }\n\n scoredResults.sort((a, b) => b.score - a.score);\n\n const limitedResults =\n this._limit < Infinity\n ? scoredResults.slice(0, this._limit)\n : scoredResults;\n\n return {\n results: limitedResults,\n matchedKeywords,\n tokens: searchTokens,\n warnings: [...new Set(allWarnings)],\n };\n }\n\n queryAsync(search: string): Promise<SpotrResult<T>> {\n return new Promise((resolve) => {\n if (this._debounce > 0) {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer);\n }\n this._debounceTimer = setTimeout(() => {\n resolve(this.query(search));\n }, this._debounce);\n } else {\n resolve(this.query(search));\n }\n });\n }\n\n private _extractKeywords(tokens: string[]): {\n keywordTokens: Map<string, string[]>;\n searchTokens: string[];\n } {\n const keywordTokens = new Map<string, string[]>();\n const searchTokens: string[] = [];\n\n for (const token of tokens) {\n const normalizedToken = this._caseSensitive ? token : token.toLowerCase();\n const keywordDef = this._keywordTriggerMap.get(normalizedToken);\n\n if (keywordDef) {\n const existing = keywordTokens.get(keywordDef.name) || [];\n existing.push(token);\n keywordTokens.set(keywordDef.name, existing);\n } else {\n searchTokens.push(token);\n }\n }\n\n return { keywordTokens, searchTokens };\n }\n\n private _applyKeywords(\n collection: T[],\n keywordTokens: Map<string, string[]>\n ): { collection: T[]; matchedKeywords: MatchedKeyword[] } {\n if (!this._keywords) {\n return { collection, matchedKeywords: [] };\n }\n\n let result = collection;\n const matchedKeywords: MatchedKeyword[] = [];\n\n const keywordNameToTerms = new Map<string, string[]>();\n for (const [name, terms] of keywordTokens) {\n keywordNameToTerms.set(name, terms);\n }\n\n if (this._keywords.mode === 'and') {\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const handlerResult = handler(result, terms);\n\n if (!Array.isArray(handlerResult)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof handlerResult}. Skipping this filter.`\n );\n // Skip this keyword - keep current collection for this step\n continue;\n }\n\n result = handlerResult;\n matchedKeywords.push({ name: def.name, terms });\n }\n }\n } else {\n const mergedResults: T[] = [];\n const processedItems = new Set<T>();\n\n for (const def of this._keywords.definitions) {\n const terms = keywordNameToTerms.get(def.name);\n if (terms) {\n const handler = def.handler as (\n collection: T[],\n matchedTerms: string[]\n ) => T[];\n const keywordResults = handler(this._collection, terms);\n\n if (!Array.isArray(keywordResults)) {\n console.error(\n `[Spotr] Keyword handler \"${def.name}\" must return an array, received ${typeof keywordResults}. Skipping this filter.`\n );\n // Skip this keyword - don't add its results\n continue;\n }\n\n matchedKeywords.push({ name: def.name, terms });\n\n for (const item of keywordResults) {\n if (!processedItems.has(item)) {\n processedItems.add(item);\n mergedResults.push(item);\n }\n }\n }\n }\n result = mergedResults;\n }\n\n return { collection: result, matchedKeywords };\n }\n}\n"],"names":["Spotr","options","__publicField","validated","validateOptions","normalizeFieldConfig","validateKeywords","map","def","triggers","trigger","collection","validateCollection","search","tokens","tokenize","keywordTokens","searchTokens","filteredCollection","matchedKeywords","result","allWarnings","item","score","warnings","scoreItem","validTokens","t","normalizedTokens","token","scoredResults","a","b","resolve","normalizedToken","keywordDef","existing","keywordNameToTerms","name","terms","handler","handlerResult","mergedResults","processedItems","keywordResults"],"mappings":"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"}
|
package/dist/bundle-size.json
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function f(n,e){const h=[];for(let t=0;t<=e.length;t++)h[t]=[t];for(let t=0;t<=n.length;t++)h[0][t]=t;for(let t=1;t<=e.length;t++)for(let c=1;c<=n.length;c++)e.charAt(t-1)===n.charAt(c-1)?h[t][c]=h[t-1][c-1]:h[t][c]=Math.min(h[t-1][c-1]+1,h[t][c-1]+1,h[t-1][c]+1);return h[e.length][n.length]}function l(n,e,h=.3){if(n===e)return 1;if(n.length===0||e.length===0)return 0;if(e.includes(n))return .9+.1*n.length/e.length;const t=f(n,e),c=Math.max(n.length,e.length),s=1-t/c;return s>=h?s:0}exports.fuzzyScore=l;exports.levenshteinDistance=f;
|
|
2
2
|
//# sourceMappingURL=levenshtein.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"levenshtein.cjs","sources":["../../src/fuzzy/levenshtein.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"levenshtein.cjs","sources":["../../src/fuzzy/levenshtein.ts"],"sourcesContent":["/**\n * Calculates the Levenshtein distance (edit distance) between two strings.\n *\n * The Levenshtein distance is the minimum number of single-character edits\n * (insertions, deletions, or substitutions) required to transform string `a`\n * into string `b`.\n *\n * Uses dynamic programming with a 2D matrix where matrix[i][j] represents\n * the minimum edit distance between the first i characters of `b` and the\n * first j characters of `a`.\n *\n * @param a - First string\n * @param b - Second string\n * @returns The Levenshtein distance between the two strings\n */\nexport function levenshteinDistance(a: string, b: string): number {\n // Initialize the dynamic programming matrix\n // matrix[i][j] = edit distance between b[0..i-1] and a[0..j-1]\n const matrix: number[][] = [];\n\n // Initialize first column: transforming empty string to b[0..i-1] requires i insertions\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n // Initialize first row: transforming a[0..j-1] to empty string requires j deletions\n for (let j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n // Fill the matrix using dynamic programming\n // For each position, consider three possible operations:\n // 1. Substitution: matrix[i-1][j-1] + 1 (if characters differ)\n // 2. Insertion: matrix[i-1][j] + 1 (insert character from b)\n // 3. Deletion: matrix[i][j-1] + 1 (delete character from a)\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n // Characters match: no edit needed, carry forward previous distance\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n // Characters differ: take minimum of three edit operations\n matrix[i][j] = Math.min(\n matrix[i - 1][j - 1] + 1, // Substitution: replace a[j-1] with b[i-1]\n matrix[i][j - 1] + 1, // Deletion: delete a[j-1]\n matrix[i - 1][j] + 1 // Insertion: insert b[i-1] into a\n );\n }\n }\n }\n\n // Return the final distance (bottom-right corner of matrix)\n return matrix[b.length][a.length];\n}\n\n/**\n * Calculates a fuzzy similarity score between two pre-normalized strings.\n * Callers must truncate and apply case normalization before calling.\n *\n * The scoring algorithm uses a tiered approach:\n * 1. Exact match: Returns score of 1.0\n * 2. Substring match: Returns score between 0.9-1.0 based on match ratio\n * 3. Levenshtein distance: Returns normalized similarity score (0-1)\n *\n * Scores below the threshold are returned as 0 to filter out poor matches.\n *\n * @param normalizedQuery - Pre-normalized query string (truncated, case-adjusted)\n * @param normalizedTarget - Pre-normalized target string (truncated, case-adjusted)\n * @param threshold - Minimum score threshold. Scores below this return 0\n * @returns Similarity score between 0 and 1\n */\nexport function fuzzyScore(\n normalizedQuery: string,\n normalizedTarget: string,\n threshold: number = 0.3\n): number {\n // Exact match: perfect score\n if (normalizedQuery === normalizedTarget) {\n return 1;\n }\n\n // Empty string: no match possible\n if (normalizedQuery.length === 0 || normalizedTarget.length === 0) {\n return 0;\n }\n\n // Substring match: query is contained within target\n // Score ranges from 0.9 (short query in long target) to 1.0 (query equals target)\n if (normalizedTarget.includes(normalizedQuery)) {\n return 0.9 + (0.1 * normalizedQuery.length) / normalizedTarget.length;\n }\n\n // Calculate Levenshtein distance and convert to similarity score\n const distance = levenshteinDistance(normalizedQuery, normalizedTarget);\n const maxLen = Math.max(normalizedQuery.length, normalizedTarget.length);\n const score = 1 - distance / maxLen;\n\n return score >= threshold ? score : 0;\n}\n"],"names":["levenshteinDistance","a","b","matrix","i","j","fuzzyScore","normalizedQuery","normalizedTarget","threshold","distance","maxLen","score"],"mappings":"gFAeO,SAASA,EAAoBC,EAAWC,EAAmB,CAGhE,MAAMC,EAAqB,CAAA,EAG3B,QAASC,EAAI,EAAGA,GAAKF,EAAE,OAAQE,IAC7BD,EAAOC,CAAC,EAAI,CAACA,CAAC,EAIhB,QAASC,EAAI,EAAGA,GAAKJ,EAAE,OAAQI,IAC7BF,EAAO,CAAC,EAAEE,CAAC,EAAIA,EAQjB,QAASD,EAAI,EAAGA,GAAKF,EAAE,OAAQE,IAC7B,QAASC,EAAI,EAAGA,GAAKJ,EAAE,OAAQI,IACzBH,EAAE,OAAOE,EAAI,CAAC,IAAMH,EAAE,OAAOI,EAAI,CAAC,EAEpCF,EAAOC,CAAC,EAAEC,CAAC,EAAIF,EAAOC,EAAI,CAAC,EAAEC,EAAI,CAAC,EAGlCF,EAAOC,CAAC,EAAEC,CAAC,EAAI,KAAK,IAClBF,EAAOC,EAAI,CAAC,EAAEC,EAAI,CAAC,EAAI,EACvBF,EAAOC,CAAC,EAAEC,EAAI,CAAC,EAAI,EACnBF,EAAOC,EAAI,CAAC,EAAEC,CAAC,EAAI,CAAA,EAO3B,OAAOF,EAAOD,EAAE,MAAM,EAAED,EAAE,MAAM,CAClC,CAkBO,SAASK,EACdC,EACAC,EACAC,EAAoB,GACZ,CAER,GAAIF,IAAoBC,EACtB,MAAO,GAIT,GAAID,EAAgB,SAAW,GAAKC,EAAiB,SAAW,EAC9D,MAAO,GAKT,GAAIA,EAAiB,SAASD,CAAe,EAC3C,MAAO,IAAO,GAAMA,EAAgB,OAAUC,EAAiB,OAIjE,MAAME,EAAWV,EAAoBO,EAAiBC,CAAgB,EAChEG,EAAS,KAAK,IAAIJ,EAAgB,OAAQC,EAAiB,MAAM,EACjEI,EAAQ,EAAIF,EAAWC,EAE7B,OAAOC,GAASH,EAAYG,EAAQ,CACtC"}
|
|
@@ -1,45 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
function s(n, h) {
|
|
2
|
+
const c = [];
|
|
3
|
+
for (let t = 0; t <= h.length; t++)
|
|
4
|
+
c[t] = [t];
|
|
5
|
+
for (let t = 0; t <= n.length; t++)
|
|
6
|
+
c[0][t] = t;
|
|
7
|
+
for (let t = 1; t <= h.length; t++)
|
|
8
|
+
for (let e = 1; e <= n.length; e++)
|
|
9
|
+
h.charAt(t - 1) === n.charAt(e - 1) ? c[t][e] = c[t - 1][e - 1] : c[t][e] = Math.min(
|
|
10
|
+
c[t - 1][e - 1] + 1,
|
|
11
|
+
// Substitution: replace a[j-1] with b[i-1]
|
|
12
|
+
c[t][e - 1] + 1,
|
|
13
|
+
// Deletion: delete a[j-1]
|
|
14
|
+
c[t - 1][e] + 1
|
|
15
|
+
// Insertion: insert b[i-1] into a
|
|
14
16
|
);
|
|
15
|
-
return
|
|
17
|
+
return c[h.length][n.length];
|
|
16
18
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
),
|
|
25
|
-
|
|
26
|
-
if (f === h)
|
|
27
|
-
return { score: 1, warnings: l };
|
|
28
|
-
if (f.length === 0 || h.length === 0)
|
|
29
|
-
return { score: 0, warnings: l };
|
|
30
|
-
if (h.includes(f))
|
|
31
|
-
return {
|
|
32
|
-
score: 0.9 + 0.1 * f.length / h.length,
|
|
33
|
-
warnings: l
|
|
34
|
-
};
|
|
35
|
-
const a = m(f, h), u = Math.max(f.length, h.length), i = 1 - a / u;
|
|
36
|
-
return {
|
|
37
|
-
score: i >= r ? i : 0,
|
|
38
|
-
warnings: l
|
|
39
|
-
};
|
|
19
|
+
function l(n, h, c = 0.3) {
|
|
20
|
+
if (n === h)
|
|
21
|
+
return 1;
|
|
22
|
+
if (n.length === 0 || h.length === 0)
|
|
23
|
+
return 0;
|
|
24
|
+
if (h.includes(n))
|
|
25
|
+
return 0.9 + 0.1 * n.length / h.length;
|
|
26
|
+
const t = s(n, h), e = Math.max(n.length, h.length), f = 1 - t / e;
|
|
27
|
+
return f >= c ? f : 0;
|
|
40
28
|
}
|
|
41
29
|
export {
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
l as fuzzyScore,
|
|
31
|
+
s as levenshteinDistance
|
|
44
32
|
};
|
|
45
33
|
//# sourceMappingURL=levenshtein.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"levenshtein.js","sources":["../../src/fuzzy/levenshtein.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"levenshtein.js","sources":["../../src/fuzzy/levenshtein.ts"],"sourcesContent":["/**\n * Calculates the Levenshtein distance (edit distance) between two strings.\n *\n * The Levenshtein distance is the minimum number of single-character edits\n * (insertions, deletions, or substitutions) required to transform string `a`\n * into string `b`.\n *\n * Uses dynamic programming with a 2D matrix where matrix[i][j] represents\n * the minimum edit distance between the first i characters of `b` and the\n * first j characters of `a`.\n *\n * @param a - First string\n * @param b - Second string\n * @returns The Levenshtein distance between the two strings\n */\nexport function levenshteinDistance(a: string, b: string): number {\n // Initialize the dynamic programming matrix\n // matrix[i][j] = edit distance between b[0..i-1] and a[0..j-1]\n const matrix: number[][] = [];\n\n // Initialize first column: transforming empty string to b[0..i-1] requires i insertions\n for (let i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n // Initialize first row: transforming a[0..j-1] to empty string requires j deletions\n for (let j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n // Fill the matrix using dynamic programming\n // For each position, consider three possible operations:\n // 1. Substitution: matrix[i-1][j-1] + 1 (if characters differ)\n // 2. Insertion: matrix[i-1][j] + 1 (insert character from b)\n // 3. Deletion: matrix[i][j-1] + 1 (delete character from a)\n for (let i = 1; i <= b.length; i++) {\n for (let j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n // Characters match: no edit needed, carry forward previous distance\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n // Characters differ: take minimum of three edit operations\n matrix[i][j] = Math.min(\n matrix[i - 1][j - 1] + 1, // Substitution: replace a[j-1] with b[i-1]\n matrix[i][j - 1] + 1, // Deletion: delete a[j-1]\n matrix[i - 1][j] + 1 // Insertion: insert b[i-1] into a\n );\n }\n }\n }\n\n // Return the final distance (bottom-right corner of matrix)\n return matrix[b.length][a.length];\n}\n\n/**\n * Calculates a fuzzy similarity score between two pre-normalized strings.\n * Callers must truncate and apply case normalization before calling.\n *\n * The scoring algorithm uses a tiered approach:\n * 1. Exact match: Returns score of 1.0\n * 2. Substring match: Returns score between 0.9-1.0 based on match ratio\n * 3. Levenshtein distance: Returns normalized similarity score (0-1)\n *\n * Scores below the threshold are returned as 0 to filter out poor matches.\n *\n * @param normalizedQuery - Pre-normalized query string (truncated, case-adjusted)\n * @param normalizedTarget - Pre-normalized target string (truncated, case-adjusted)\n * @param threshold - Minimum score threshold. Scores below this return 0\n * @returns Similarity score between 0 and 1\n */\nexport function fuzzyScore(\n normalizedQuery: string,\n normalizedTarget: string,\n threshold: number = 0.3\n): number {\n // Exact match: perfect score\n if (normalizedQuery === normalizedTarget) {\n return 1;\n }\n\n // Empty string: no match possible\n if (normalizedQuery.length === 0 || normalizedTarget.length === 0) {\n return 0;\n }\n\n // Substring match: query is contained within target\n // Score ranges from 0.9 (short query in long target) to 1.0 (query equals target)\n if (normalizedTarget.includes(normalizedQuery)) {\n return 0.9 + (0.1 * normalizedQuery.length) / normalizedTarget.length;\n }\n\n // Calculate Levenshtein distance and convert to similarity score\n const distance = levenshteinDistance(normalizedQuery, normalizedTarget);\n const maxLen = Math.max(normalizedQuery.length, normalizedTarget.length);\n const score = 1 - distance / maxLen;\n\n return score >= threshold ? score : 0;\n}\n"],"names":["levenshteinDistance","a","b","matrix","i","j","fuzzyScore","normalizedQuery","normalizedTarget","threshold","distance","maxLen","score"],"mappings":"AAeO,SAASA,EAAoBC,GAAWC,GAAmB;AAGhE,QAAMC,IAAqB,CAAA;AAG3B,WAASC,IAAI,GAAGA,KAAKF,EAAE,QAAQE;AAC7B,IAAAD,EAAOC,CAAC,IAAI,CAACA,CAAC;AAIhB,WAASC,IAAI,GAAGA,KAAKJ,EAAE,QAAQI;AAC7B,IAAAF,EAAO,CAAC,EAAEE,CAAC,IAAIA;AAQjB,WAASD,IAAI,GAAGA,KAAKF,EAAE,QAAQE;AAC7B,aAASC,IAAI,GAAGA,KAAKJ,EAAE,QAAQI;AAC7B,MAAIH,EAAE,OAAOE,IAAI,CAAC,MAAMH,EAAE,OAAOI,IAAI,CAAC,IAEpCF,EAAOC,CAAC,EAAEC,CAAC,IAAIF,EAAOC,IAAI,CAAC,EAAEC,IAAI,CAAC,IAGlCF,EAAOC,CAAC,EAAEC,CAAC,IAAI,KAAK;AAAA,QAClBF,EAAOC,IAAI,CAAC,EAAEC,IAAI,CAAC,IAAI;AAAA;AAAA,QACvBF,EAAOC,CAAC,EAAEC,IAAI,CAAC,IAAI;AAAA;AAAA,QACnBF,EAAOC,IAAI,CAAC,EAAEC,CAAC,IAAI;AAAA;AAAA,MAAA;AAO3B,SAAOF,EAAOD,EAAE,MAAM,EAAED,EAAE,MAAM;AAClC;AAkBO,SAASK,EACdC,GACAC,GACAC,IAAoB,KACZ;AAER,MAAIF,MAAoBC;AACtB,WAAO;AAIT,MAAID,EAAgB,WAAW,KAAKC,EAAiB,WAAW;AAC9D,WAAO;AAKT,MAAIA,EAAiB,SAASD,CAAe;AAC3C,WAAO,MAAO,MAAMA,EAAgB,SAAUC,EAAiB;AAIjE,QAAME,IAAWV,EAAoBO,GAAiBC,CAAgB,GAChEG,IAAS,KAAK,IAAIJ,EAAgB,QAAQC,EAAiB,MAAM,GACjEI,IAAQ,IAAIF,IAAWC;AAE7B,SAAOC,KAASH,IAAYG,IAAQ;AACtC;"}
|
package/dist/fuzzy/scorer.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("./levenshtein.cjs"),p=require("../utils/nested.cjs"),w=require("../types.cjs");function z(r,o){return r.map(e=>typeof e=="string"?{name:e,weight:1,threshold:o}:{name:e.name,weight:e.weight??1,threshold:e.threshold??o})}function T(r,o,e,h,s=w.MAX_STRING_LENGTH){let l=0,i=0;const c=[];for(const t of e){const u=p.getNestedValue(r,t.name);if(u==null){t.name.includes(".")&&c.push(`Field "${t.name}" not found on item`);continue}let n=String(u);n.length>s&&(c.push(`Target string truncated from ${n.length} to ${s} characters for performance`),n=n.slice(0,s));const f=h?n:n.toLowerCase();let a=0;for(const m of o){const g=d.fuzzyScore(m,f,t.threshold);a=Math.max(a,g)}l+=a*t.weight,i+=t.weight}return{score:i>0?l/i:0,warnings:c}}exports.normalizeFieldConfig=z;exports.scoreItem=T;
|
|
2
2
|
//# sourceMappingURL=scorer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scorer.cjs","sources":["../../src/fuzzy/scorer.ts"],"sourcesContent":["import { fuzzyScore } from './levenshtein';\nimport { getNestedValue } from '../utils/nested';\nimport { MAX_STRING_LENGTH } from '../types';\nimport type { FieldConfig, NormalizedFieldConfig } from '../types';\n\nexport function normalizeFieldConfig(\n fields: FieldConfig[],\n globalThreshold: number\n): NormalizedFieldConfig[] {\n return fields.map((field) => {\n if (typeof field === 'string') {\n return { name: field, weight: 1, threshold: globalThreshold };\n }\n return {\n name: field.name,\n weight: field.weight ?? 1,\n threshold: field.threshold ?? globalThreshold,\n };\n });\n}\n\nexport function scoreItem<T>(\n item: T,\n
|
|
1
|
+
{"version":3,"file":"scorer.cjs","sources":["../../src/fuzzy/scorer.ts"],"sourcesContent":["import { fuzzyScore } from './levenshtein';\nimport { getNestedValue } from '../utils/nested';\nimport { MAX_STRING_LENGTH } from '../types';\nimport type { FieldConfig, NormalizedFieldConfig } from '../types';\n\nexport function normalizeFieldConfig(\n fields: FieldConfig[],\n globalThreshold: number\n): NormalizedFieldConfig[] {\n return fields.map((field) => {\n if (typeof field === 'string') {\n return { name: field, weight: 1, threshold: globalThreshold };\n }\n return {\n name: field.name,\n weight: field.weight ?? 1,\n threshold: field.threshold ?? globalThreshold,\n };\n });\n}\n\nexport function scoreItem<T>(\n item: T,\n normalizedTokens: string[],\n fields: NormalizedFieldConfig[],\n caseSensitive: boolean,\n maxStringLength: number = MAX_STRING_LENGTH\n): { score: number; warnings: string[] } {\n let totalScore = 0;\n let totalWeight = 0;\n const warnings: string[] = [];\n\n for (const field of fields) {\n const value = getNestedValue(item, field.name);\n\n if (value == null) {\n if (field.name.includes('.')) {\n warnings.push(`Field \"${field.name}\" not found on item`);\n }\n continue;\n }\n\n let stringValue = String(value);\n\n // Normalize target once per field (truncate, collect warnings)\n if (stringValue.length > maxStringLength) {\n warnings.push(\n `Target string truncated from ${stringValue.length} to ${maxStringLength} characters for performance`\n );\n stringValue = stringValue.slice(0, maxStringLength);\n }\n const normalizedTarget = caseSensitive\n ? stringValue\n : stringValue.toLowerCase();\n\n let bestTokenScore = 0;\n for (const normalizedToken of normalizedTokens) {\n const score = fuzzyScore(\n normalizedToken,\n normalizedTarget,\n field.threshold\n );\n bestTokenScore = Math.max(bestTokenScore, score);\n }\n\n totalScore += bestTokenScore * field.weight;\n totalWeight += field.weight;\n }\n\n return {\n score: totalWeight > 0 ? totalScore / totalWeight : 0,\n warnings,\n };\n}\n"],"names":["normalizeFieldConfig","fields","globalThreshold","field","scoreItem","item","normalizedTokens","caseSensitive","maxStringLength","MAX_STRING_LENGTH","totalScore","totalWeight","warnings","value","getNestedValue","stringValue","normalizedTarget","bestTokenScore","normalizedToken","score","fuzzyScore"],"mappings":"gLAKO,SAASA,EACdC,EACAC,EACyB,CACzB,OAAOD,EAAO,IAAKE,GACb,OAAOA,GAAU,SACZ,CAAE,KAAMA,EAAO,OAAQ,EAAG,UAAWD,CAAA,EAEvC,CACL,KAAMC,EAAM,KACZ,OAAQA,EAAM,QAAU,EACxB,UAAWA,EAAM,WAAaD,CAAA,CAEjC,CACH,CAEO,SAASE,EACdC,EACAC,EACAL,EACAM,EACAC,EAA0BC,oBACa,CACvC,IAAIC,EAAa,EACbC,EAAc,EAClB,MAAMC,EAAqB,CAAA,EAE3B,UAAWT,KAASF,EAAQ,CAC1B,MAAMY,EAAQC,EAAAA,eAAeT,EAAMF,EAAM,IAAI,EAE7C,GAAIU,GAAS,KAAM,CACbV,EAAM,KAAK,SAAS,GAAG,GACzBS,EAAS,KAAK,UAAUT,EAAM,IAAI,qBAAqB,EAEzD,QACF,CAEA,IAAIY,EAAc,OAAOF,CAAK,EAG1BE,EAAY,OAASP,IACvBI,EAAS,KACP,gCAAgCG,EAAY,MAAM,OAAOP,CAAe,6BAAA,EAE1EO,EAAcA,EAAY,MAAM,EAAGP,CAAe,GAEpD,MAAMQ,EAAmBT,EACrBQ,EACAA,EAAY,YAAA,EAEhB,IAAIE,EAAiB,EACrB,UAAWC,KAAmBZ,EAAkB,CAC9C,MAAMa,EAAQC,EAAAA,WACZF,EACAF,EACAb,EAAM,SAAA,EAERc,EAAiB,KAAK,IAAIA,EAAgBE,CAAK,CACjD,CAEAT,GAAcO,EAAiBd,EAAM,OACrCQ,GAAeR,EAAM,MACvB,CAEA,MAAO,CACL,MAAOQ,EAAc,EAAID,EAAaC,EAAc,EACpD,SAAAC,CAAA,CAEJ"}
|
package/dist/fuzzy/scorer.js
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
|
-
import { fuzzyScore as
|
|
2
|
-
import { getNestedValue as
|
|
1
|
+
import { fuzzyScore as p } from "./levenshtein.js";
|
|
2
|
+
import { getNestedValue as d } from "../utils/nested.js";
|
|
3
3
|
import { MAX_STRING_LENGTH as w } from "../types.js";
|
|
4
|
-
function
|
|
5
|
-
return
|
|
6
|
-
name:
|
|
7
|
-
weight:
|
|
8
|
-
threshold:
|
|
4
|
+
function S(n, r) {
|
|
5
|
+
return n.map((e) => typeof e == "string" ? { name: e, weight: 1, threshold: r } : {
|
|
6
|
+
name: e.name,
|
|
7
|
+
weight: e.weight ?? 1,
|
|
8
|
+
threshold: e.threshold ?? r
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
let m = 0,
|
|
13
|
-
const
|
|
14
|
-
for (const
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
|
|
11
|
+
function $(n, r, e, f, i = w) {
|
|
12
|
+
let m = 0, s = 0;
|
|
13
|
+
const a = [];
|
|
14
|
+
for (const t of e) {
|
|
15
|
+
const l = d(n, t.name);
|
|
16
|
+
if (l == null) {
|
|
17
|
+
t.name.includes(".") && a.push(`Field "${t.name}" not found on item`);
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
h
|
|
20
|
+
let o = String(l);
|
|
21
|
+
o.length > i && (a.push(
|
|
22
|
+
`Target string truncated from ${o.length} to ${i} characters for performance`
|
|
23
|
+
), o = o.slice(0, i));
|
|
24
|
+
const u = f ? o : o.toLowerCase();
|
|
25
|
+
let c = 0;
|
|
26
|
+
for (const h of r) {
|
|
27
|
+
const g = p(
|
|
28
|
+
h,
|
|
29
|
+
u,
|
|
30
|
+
t.threshold
|
|
29
31
|
);
|
|
30
|
-
|
|
32
|
+
c = Math.max(c, g);
|
|
31
33
|
}
|
|
32
|
-
m +=
|
|
34
|
+
m += c * t.weight, s += t.weight;
|
|
33
35
|
}
|
|
34
36
|
return {
|
|
35
|
-
score:
|
|
36
|
-
warnings:
|
|
37
|
+
score: s > 0 ? m / s : 0,
|
|
38
|
+
warnings: a
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
export {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
S as normalizeFieldConfig,
|
|
43
|
+
$ as scoreItem
|
|
42
44
|
};
|
|
43
45
|
//# sourceMappingURL=scorer.js.map
|
package/dist/fuzzy/scorer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scorer.js","sources":["../../src/fuzzy/scorer.ts"],"sourcesContent":["import { fuzzyScore } from './levenshtein';\nimport { getNestedValue } from '../utils/nested';\nimport { MAX_STRING_LENGTH } from '../types';\nimport type { FieldConfig, NormalizedFieldConfig } from '../types';\n\nexport function normalizeFieldConfig(\n fields: FieldConfig[],\n globalThreshold: number\n): NormalizedFieldConfig[] {\n return fields.map((field) => {\n if (typeof field === 'string') {\n return { name: field, weight: 1, threshold: globalThreshold };\n }\n return {\n name: field.name,\n weight: field.weight ?? 1,\n threshold: field.threshold ?? globalThreshold,\n };\n });\n}\n\nexport function scoreItem<T>(\n item: T,\n
|
|
1
|
+
{"version":3,"file":"scorer.js","sources":["../../src/fuzzy/scorer.ts"],"sourcesContent":["import { fuzzyScore } from './levenshtein';\nimport { getNestedValue } from '../utils/nested';\nimport { MAX_STRING_LENGTH } from '../types';\nimport type { FieldConfig, NormalizedFieldConfig } from '../types';\n\nexport function normalizeFieldConfig(\n fields: FieldConfig[],\n globalThreshold: number\n): NormalizedFieldConfig[] {\n return fields.map((field) => {\n if (typeof field === 'string') {\n return { name: field, weight: 1, threshold: globalThreshold };\n }\n return {\n name: field.name,\n weight: field.weight ?? 1,\n threshold: field.threshold ?? globalThreshold,\n };\n });\n}\n\nexport function scoreItem<T>(\n item: T,\n normalizedTokens: string[],\n fields: NormalizedFieldConfig[],\n caseSensitive: boolean,\n maxStringLength: number = MAX_STRING_LENGTH\n): { score: number; warnings: string[] } {\n let totalScore = 0;\n let totalWeight = 0;\n const warnings: string[] = [];\n\n for (const field of fields) {\n const value = getNestedValue(item, field.name);\n\n if (value == null) {\n if (field.name.includes('.')) {\n warnings.push(`Field \"${field.name}\" not found on item`);\n }\n continue;\n }\n\n let stringValue = String(value);\n\n // Normalize target once per field (truncate, collect warnings)\n if (stringValue.length > maxStringLength) {\n warnings.push(\n `Target string truncated from ${stringValue.length} to ${maxStringLength} characters for performance`\n );\n stringValue = stringValue.slice(0, maxStringLength);\n }\n const normalizedTarget = caseSensitive\n ? stringValue\n : stringValue.toLowerCase();\n\n let bestTokenScore = 0;\n for (const normalizedToken of normalizedTokens) {\n const score = fuzzyScore(\n normalizedToken,\n normalizedTarget,\n field.threshold\n );\n bestTokenScore = Math.max(bestTokenScore, score);\n }\n\n totalScore += bestTokenScore * field.weight;\n totalWeight += field.weight;\n }\n\n return {\n score: totalWeight > 0 ? totalScore / totalWeight : 0,\n warnings,\n };\n}\n"],"names":["normalizeFieldConfig","fields","globalThreshold","field","scoreItem","item","normalizedTokens","caseSensitive","maxStringLength","MAX_STRING_LENGTH","totalScore","totalWeight","warnings","value","getNestedValue","stringValue","normalizedTarget","bestTokenScore","normalizedToken","score","fuzzyScore"],"mappings":";;;AAKO,SAASA,EACdC,GACAC,GACyB;AACzB,SAAOD,EAAO,IAAI,CAACE,MACb,OAAOA,KAAU,WACZ,EAAE,MAAMA,GAAO,QAAQ,GAAG,WAAWD,EAAA,IAEvC;AAAA,IACL,MAAMC,EAAM;AAAA,IACZ,QAAQA,EAAM,UAAU;AAAA,IACxB,WAAWA,EAAM,aAAaD;AAAA,EAAA,CAEjC;AACH;AAEO,SAASE,EACdC,GACAC,GACAL,GACAM,GACAC,IAA0BC,GACa;AACvC,MAAIC,IAAa,GACbC,IAAc;AAClB,QAAMC,IAAqB,CAAA;AAE3B,aAAWT,KAASF,GAAQ;AAC1B,UAAMY,IAAQC,EAAeT,GAAMF,EAAM,IAAI;AAE7C,QAAIU,KAAS,MAAM;AACjB,MAAIV,EAAM,KAAK,SAAS,GAAG,KACzBS,EAAS,KAAK,UAAUT,EAAM,IAAI,qBAAqB;AAEzD;AAAA,IACF;AAEA,QAAIY,IAAc,OAAOF,CAAK;AAG9B,IAAIE,EAAY,SAASP,MACvBI,EAAS;AAAA,MACP,gCAAgCG,EAAY,MAAM,OAAOP,CAAe;AAAA,IAAA,GAE1EO,IAAcA,EAAY,MAAM,GAAGP,CAAe;AAEpD,UAAMQ,IAAmBT,IACrBQ,IACAA,EAAY,YAAA;AAEhB,QAAIE,IAAiB;AACrB,eAAWC,KAAmBZ,GAAkB;AAC9C,YAAMa,IAAQC;AAAA,QACZF;AAAA,QACAF;AAAA,QACAb,EAAM;AAAA,MAAA;AAER,MAAAc,IAAiB,KAAK,IAAIA,GAAgBE,CAAK;AAAA,IACjD;AAEA,IAAAT,KAAcO,IAAiBd,EAAM,QACrCQ,KAAeR,EAAM;AAAA,EACvB;AAEA,SAAO;AAAA,IACL,OAAOQ,IAAc,IAAID,IAAaC,IAAc;AAAA,IACpD,UAAAC;AAAA,EAAA;AAEJ;"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./Spotr.cjs"),r=require("./errors.cjs"),t=require("./types.cjs"),o=require("./utils/nested.cjs");exports.Spotr=e.Spotr;exports.ErrorCodes=r.ErrorCodes;exports.SpotrError=r.SpotrError;exports.MAX_STRING_LENGTH=t.MAX_STRING_LENGTH;exports.getNestedValue=o.getNestedValue;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { Spotr as
|
|
2
|
-
import { ErrorCodes as
|
|
1
|
+
import { Spotr as e } from "./Spotr.js";
|
|
2
|
+
import { ErrorCodes as p, SpotrError as f } from "./errors.js";
|
|
3
3
|
import { MAX_STRING_LENGTH as x } from "./types.js";
|
|
4
|
+
import { getNestedValue as N } from "./utils/nested.js";
|
|
4
5
|
export {
|
|
5
|
-
|
|
6
|
+
p as ErrorCodes,
|
|
6
7
|
x as MAX_STRING_LENGTH,
|
|
7
|
-
|
|
8
|
-
f as SpotrError
|
|
8
|
+
e as Spotr,
|
|
9
|
+
f as SpotrError,
|
|
10
|
+
N as getNestedValue
|
|
9
11
|
};
|
|
10
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
package/dist/preact.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("preact/hooks"),o=require("./Spotr.cjs")
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("preact/hooks"),o=require("./Spotr.cjs"),n=require("./utils/shallowEqual.cjs");function c(t){const r=u.useRef(t);n.shallowEqual(r.current,t)||(r.current=t);const e=u.useRef(null);return(!e.current||e.current.options!==r.current)&&(e.current=new o.Spotr(r.current)),e.current}exports.Spotr=o.Spotr;exports.useSpotr=c;
|
|
2
2
|
//# sourceMappingURL=preact.cjs.map
|
package/dist/preact.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preact.cjs","sources":["../src/preact/index.ts"],"sourcesContent":["import { useRef } from 'preact/hooks';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\
|
|
1
|
+
{"version":3,"file":"preact.cjs","sources":["../src/preact/index.ts"],"sourcesContent":["import { useRef } from 'preact/hooks';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\nimport { shallowEqual } from '../utils';\n\n// Overload: infer T from collection\nexport function useSpotr<C extends readonly object[] | object[] | Set<object>>(\n options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n }\n): Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T>;\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T> {\n const optionsRef = useRef(options);\n\n if (!shallowEqual(optionsRef.current, options)) {\n optionsRef.current = options;\n }\n\n const spotrRef = useRef<Spotr<T> | null>(null);\n\n if (!spotrRef.current || spotrRef.current.options !== optionsRef.current) {\n spotrRef.current = new Spotr(optionsRef.current);\n }\n\n return spotrRef.current;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["useSpotr","options","optionsRef","useRef","shallowEqual","spotrRef","Spotr"],"mappings":"+KAaO,SAASA,EAA2BC,EAAoC,CAC7E,MAAMC,EAAaC,EAAAA,OAAOF,CAAO,EAE5BG,EAAAA,aAAaF,EAAW,QAASD,CAAO,IAC3CC,EAAW,QAAUD,GAGvB,MAAMI,EAAWF,EAAAA,OAAwB,IAAI,EAE7C,OAAI,CAACE,EAAS,SAAWA,EAAS,QAAQ,UAAYH,EAAW,WAC/DG,EAAS,QAAU,IAAIC,QAAMJ,EAAW,OAAO,GAG1CG,EAAS,OAClB"}
|
package/dist/preact.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { useRef as
|
|
2
|
-
import { Spotr as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const t =
|
|
8
|
-
|
|
9
|
-
for (const n of t)
|
|
10
|
-
if (r[n] !== e[n])
|
|
11
|
-
return !1;
|
|
12
|
-
return !0;
|
|
13
|
-
}
|
|
14
|
-
function i(r) {
|
|
15
|
-
const e = u(r);
|
|
16
|
-
c(e.current, r) || (e.current = r);
|
|
17
|
-
const t = u(null);
|
|
18
|
-
return (!t.current || t.current.options !== e.current) && (t.current = new f(e.current)), t.current;
|
|
1
|
+
import { useRef as n } from "preact/hooks";
|
|
2
|
+
import { Spotr as o } from "./Spotr.js";
|
|
3
|
+
import { shallowEqual as u } from "./utils/shallowEqual.js";
|
|
4
|
+
function i(e) {
|
|
5
|
+
const r = n(e);
|
|
6
|
+
u(r.current, e) || (r.current = e);
|
|
7
|
+
const t = n(null);
|
|
8
|
+
return (!t.current || t.current.options !== r.current) && (t.current = new o(r.current)), t.current;
|
|
19
9
|
}
|
|
20
10
|
export {
|
|
21
|
-
|
|
11
|
+
o as Spotr,
|
|
22
12
|
i as useSpotr
|
|
23
13
|
};
|
|
24
14
|
//# sourceMappingURL=preact.js.map
|
package/dist/preact.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preact.js","sources":["../src/preact/index.ts"],"sourcesContent":["import { useRef } from 'preact/hooks';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\
|
|
1
|
+
{"version":3,"file":"preact.js","sources":["../src/preact/index.ts"],"sourcesContent":["import { useRef } from 'preact/hooks';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\nimport { shallowEqual } from '../utils';\n\n// Overload: infer T from collection\nexport function useSpotr<C extends readonly object[] | object[] | Set<object>>(\n options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n }\n): Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T>;\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T> {\n const optionsRef = useRef(options);\n\n if (!shallowEqual(optionsRef.current, options)) {\n optionsRef.current = options;\n }\n\n const spotrRef = useRef<Spotr<T> | null>(null);\n\n if (!spotrRef.current || spotrRef.current.options !== optionsRef.current) {\n spotrRef.current = new Spotr(optionsRef.current);\n }\n\n return spotrRef.current;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["useSpotr","options","optionsRef","useRef","shallowEqual","spotrRef","Spotr"],"mappings":";;;AAaO,SAASA,EAA2BC,GAAoC;AAC7E,QAAMC,IAAaC,EAAOF,CAAO;AAEjC,EAAKG,EAAaF,EAAW,SAASD,CAAO,MAC3CC,EAAW,UAAUD;AAGvB,QAAMI,IAAWF,EAAwB,IAAI;AAE7C,UAAI,CAACE,EAAS,WAAWA,EAAS,QAAQ,YAAYH,EAAW,aAC/DG,EAAS,UAAU,IAAIC,EAAMJ,EAAW,OAAO,IAG1CG,EAAS;AAClB;"}
|
package/dist/react.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),n=require("./Spotr.cjs"),o=require("./utils/shallowEqual.cjs");function c(t){const r=u.useRef(t);o.shallowEqual(r.current,t)||(r.current=t);const e=u.useRef(null);return(!e.current||e.current.options!==r.current)&&(e.current=new n.Spotr(r.current)),e.current}exports.Spotr=n.Spotr;exports.useSpotr=c;
|
|
2
2
|
//# sourceMappingURL=react.cjs.map
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.cjs","sources":["../src/react/index.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\
|
|
1
|
+
{"version":3,"file":"react.cjs","sources":["../src/react/index.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\nimport { shallowEqual } from '../utils';\n\n// Overload: infer T from collection\nexport function useSpotr<C extends readonly object[] | object[] | Set<object>>(\n options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n }\n): Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T>;\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T> {\n const optionsRef = useRef(options);\n\n if (!shallowEqual(optionsRef.current, options)) {\n optionsRef.current = options;\n }\n\n const spotrRef = useRef<Spotr<T> | null>(null);\n\n if (!spotrRef.current || spotrRef.current.options !== optionsRef.current) {\n spotrRef.current = new Spotr(optionsRef.current);\n }\n\n return spotrRef.current;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["useSpotr","options","optionsRef","useRef","shallowEqual","spotrRef","Spotr"],"mappings":"wKAaO,SAASA,EAA2BC,EAAoC,CAC7E,MAAMC,EAAaC,EAAAA,OAAOF,CAAO,EAE5BG,EAAAA,aAAaF,EAAW,QAASD,CAAO,IAC3CC,EAAW,QAAUD,GAGvB,MAAMI,EAAWF,EAAAA,OAAwB,IAAI,EAE7C,OAAI,CAACE,EAAS,SAAWA,EAAS,QAAQ,UAAYH,EAAW,WAC/DG,EAAS,QAAU,IAAIC,QAAMJ,EAAW,OAAO,GAG1CG,EAAS,OAClB"}
|
package/dist/react.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { useRef as
|
|
2
|
-
import { Spotr as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const t =
|
|
8
|
-
|
|
9
|
-
for (const n of t)
|
|
10
|
-
if (r[n] !== e[n])
|
|
11
|
-
return !1;
|
|
12
|
-
return !0;
|
|
13
|
-
}
|
|
14
|
-
function i(r) {
|
|
15
|
-
const e = u(r);
|
|
16
|
-
c(e.current, r) || (e.current = r);
|
|
17
|
-
const t = u(null);
|
|
18
|
-
return (!t.current || t.current.options !== e.current) && (t.current = new f(e.current)), t.current;
|
|
1
|
+
import { useRef as n } from "react";
|
|
2
|
+
import { Spotr as o } from "./Spotr.js";
|
|
3
|
+
import { shallowEqual as u } from "./utils/shallowEqual.js";
|
|
4
|
+
function i(e) {
|
|
5
|
+
const r = n(e);
|
|
6
|
+
u(r.current, e) || (r.current = e);
|
|
7
|
+
const t = n(null);
|
|
8
|
+
return (!t.current || t.current.options !== r.current) && (t.current = new o(r.current)), t.current;
|
|
19
9
|
}
|
|
20
10
|
export {
|
|
21
|
-
|
|
11
|
+
o as Spotr,
|
|
22
12
|
i as useSpotr
|
|
23
13
|
};
|
|
24
14
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sources":["../src/react/index.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\
|
|
1
|
+
{"version":3,"file":"react.js","sources":["../src/react/index.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\nimport { shallowEqual } from '../utils';\n\n// Overload: infer T from collection\nexport function useSpotr<C extends readonly object[] | object[] | Set<object>>(\n options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n }\n): Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T>;\nexport function useSpotr<T extends object>(options: SpotrOptions<T>): Spotr<T> {\n const optionsRef = useRef(options);\n\n if (!shallowEqual(optionsRef.current, options)) {\n optionsRef.current = options;\n }\n\n const spotrRef = useRef<Spotr<T> | null>(null);\n\n if (!spotrRef.current || spotrRef.current.options !== optionsRef.current) {\n spotrRef.current = new Spotr(optionsRef.current);\n }\n\n return spotrRef.current;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["useSpotr","options","optionsRef","useRef","shallowEqual","spotrRef","Spotr"],"mappings":";;;AAaO,SAASA,EAA2BC,GAAoC;AAC7E,QAAMC,IAAaC,EAAOF,CAAO;AAEjC,EAAKG,EAAaF,EAAW,SAASD,CAAO,MAC3CC,EAAW,UAAUD;AAGvB,QAAMI,IAAWF,EAAwB,IAAI;AAE7C,UAAI,CAACE,EAAS,WAAWA,EAAS,QAAQ,YAAYH,EAAW,aAC/DG,EAAS,UAAU,IAAIC,EAAMJ,EAAW,OAAO,IAG1CG,EAAS;AAClB;"}
|
package/dist/solid.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("solid-js"),t=require("./Spotr.cjs");function c(e){return o.createMemo(()=>{const r=typeof e=="function"?e():e;return new t.Spotr(r)})}exports.Spotr=t.Spotr;exports.createSpotr=c;
|
|
2
2
|
//# sourceMappingURL=solid.cjs.map
|
package/dist/solid.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solid.cjs","sources":["../src/solid/index.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"solid.cjs","sources":["../src/solid/index.ts"],"sourcesContent":["import { createMemo } from 'solid-js';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\n\n// Overload: infer T from collection\nexport function createSpotr<\n C extends readonly object[] | object[] | Set<object>,\n>(\n options:\n | (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C })\n | (() => Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n })\n): () => Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | (() => SpotrOptions<T>)\n): () => Spotr<T>;\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | (() => SpotrOptions<T>)\n): () => Spotr<T> {\n return createMemo(() => {\n const opts = typeof options === 'function' ? options() : options;\n return new Spotr(opts);\n }) as () => Spotr<T>;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["createSpotr","options","createMemo","opts","Spotr"],"mappings":"qIAkBO,SAASA,EACdC,EACgB,CAChB,OAAOC,EAAAA,WAAW,IAAM,CACtB,MAAMC,EAAO,OAAOF,GAAY,WAAaA,IAAYA,EACzD,OAAO,IAAIG,EAAAA,MAAMD,CAAI,CACvB,CAAC,CACH"}
|
package/dist/solid.d.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
export declare function createSpotr<C extends readonly object[] | object[] | Set<object>>(options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
1
|
+
export declare function createSpotr<C extends readonly object[] | object[] | Set<object>>(options: (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
2
2
|
collection: C;
|
|
3
|
-
})
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
results: () => SpotrResult<ExtractItemType<C> & object>;
|
|
7
|
-
};
|
|
3
|
+
}) | (() => Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
4
|
+
collection: C;
|
|
5
|
+
})): () => Spotr<ExtractItemType<C> & object>;
|
|
8
6
|
|
|
9
|
-
export declare function createSpotr<T extends object>(options: SpotrOptions<T>):
|
|
10
|
-
query: () => string;
|
|
11
|
-
setQuery: (query: string) => void;
|
|
12
|
-
results: () => SpotrResult<T>;
|
|
13
|
-
};
|
|
7
|
+
export declare function createSpotr<T extends object>(options: SpotrOptions<T> | (() => SpotrOptions<T>)): () => Spotr<T>;
|
|
14
8
|
|
|
15
9
|
/* Excluded from this release type: ExtractItemType */
|
|
16
10
|
|
package/dist/solid.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Spotr as
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { createMemo as e } from "solid-js";
|
|
2
|
+
import { Spotr as o } from "./Spotr.js";
|
|
3
|
+
function f(r) {
|
|
4
|
+
return e(() => {
|
|
5
|
+
const t = typeof r == "function" ? r() : r;
|
|
6
|
+
return new o(t);
|
|
7
|
+
});
|
|
6
8
|
}
|
|
7
9
|
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
o as Spotr,
|
|
11
|
+
f as createSpotr
|
|
10
12
|
};
|
|
11
13
|
//# sourceMappingURL=solid.js.map
|
package/dist/solid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solid.js","sources":["../src/solid/index.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"solid.js","sources":["../src/solid/index.ts"],"sourcesContent":["import { createMemo } from 'solid-js';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\n\n// Overload: infer T from collection\nexport function createSpotr<\n C extends readonly object[] | object[] | Set<object>,\n>(\n options:\n | (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C })\n | (() => Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {\n collection: C;\n })\n): () => Spotr<ExtractItemType<C> & object>;\n// Overload: explicit generic\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | (() => SpotrOptions<T>)\n): () => Spotr<T>;\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | (() => SpotrOptions<T>)\n): () => Spotr<T> {\n return createMemo(() => {\n const opts = typeof options === 'function' ? options() : options;\n return new Spotr(opts);\n }) as () => Spotr<T>;\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["createSpotr","options","createMemo","opts","Spotr"],"mappings":";;AAkBO,SAASA,EACdC,GACgB;AAChB,SAAOC,EAAW,MAAM;AACtB,UAAMC,IAAO,OAAOF,KAAY,aAAaA,MAAYA;AACzD,WAAO,IAAIG,EAAMD,CAAI;AAAA,EACvB,CAAC;AACH;"}
|
package/dist/svelte.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("svelte/store"),r=require("./Spotr.cjs");function c(e){return e!=null&&typeof e.subscribe=="function"?t.derived(e,u=>new r.Spotr(u)):t.readable(new r.Spotr(e))}exports.Spotr=r.Spotr;exports.createSpotr=c;
|
|
2
2
|
//# sourceMappingURL=svelte.cjs.map
|
package/dist/svelte.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte.cjs","sources":["../src/svelte/index.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"svelte.cjs","sources":["../src/svelte/index.ts"],"sourcesContent":["import { readable, derived } from 'svelte/store';\nimport type { Readable } from 'svelte/store';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\n\n// Overload: infer T from collection\nexport function createSpotr<\n C extends readonly object[] | object[] | Set<object>,\n>(\n options:\n | (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C })\n | Readable<\n Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C }\n >\n): Readable<Spotr<ExtractItemType<C> & object>>;\n// Overload: explicit generic\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | Readable<SpotrOptions<T>>\n): Readable<Spotr<T>>;\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | Readable<SpotrOptions<T>>\n): Readable<Spotr<T>> {\n const hasSubscribe =\n options != null &&\n typeof (options as Readable<SpotrOptions<T>>).subscribe === 'function';\n\n if (hasSubscribe) {\n return derived(\n options as Readable<SpotrOptions<T>>,\n (opts) => new Spotr(opts)\n );\n }\n return readable(new Spotr(options as SpotrOptions<T>));\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["createSpotr","options","derived","opts","Spotr","readable"],"mappings":"yIAmBO,SAASA,EACdC,EACoB,CAKpB,OAHEA,GAAW,MACX,OAAQA,EAAsC,WAAc,WAGrDC,EAAAA,QACLD,EACCE,GAAS,IAAIC,EAAAA,MAAMD,CAAI,CAAA,EAGrBE,WAAS,IAAID,QAAMH,CAA0B,CAAC,CACvD"}
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { Readable } from 'svelte/store';
|
|
2
|
-
import { Writable } from 'svelte/store';
|
|
3
2
|
|
|
4
|
-
export declare function createSpotr<C extends readonly object[] | object[] | Set<object>>(options: Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
3
|
+
export declare function createSpotr<C extends readonly object[] | object[] | Set<object>>(options: (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
5
4
|
collection: C;
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
results: Readable<SpotrResult<ExtractItemType<C> & object>>;
|
|
10
|
-
};
|
|
5
|
+
}) | Readable<Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & {
|
|
6
|
+
collection: C;
|
|
7
|
+
}>): Readable<Spotr<ExtractItemType<C> & object>>;
|
|
11
8
|
|
|
12
|
-
export declare function createSpotr<T extends object>(options: SpotrOptions<T>):
|
|
13
|
-
spotr: Spotr<T>;
|
|
14
|
-
query: Writable<string>;
|
|
15
|
-
results: Readable<SpotrResult<T>>;
|
|
16
|
-
};
|
|
9
|
+
export declare function createSpotr<T extends object>(options: SpotrOptions<T> | Readable<SpotrOptions<T>>): Readable<Spotr<T>>;
|
|
17
10
|
|
|
18
11
|
/* Excluded from this release type: ExtractItemType */
|
|
19
12
|
|
package/dist/svelte.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Spotr as
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { derived as u, readable as b } from "svelte/store";
|
|
2
|
+
import { Spotr as e } from "./Spotr.js";
|
|
3
|
+
function i(r) {
|
|
4
|
+
return r != null && typeof r.subscribe == "function" ? u(
|
|
5
|
+
r,
|
|
6
|
+
(t) => new e(t)
|
|
7
|
+
) : b(new e(r));
|
|
6
8
|
}
|
|
7
9
|
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
e as Spotr,
|
|
11
|
+
i as createSpotr
|
|
10
12
|
};
|
|
11
13
|
//# sourceMappingURL=svelte.js.map
|
package/dist/svelte.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte.js","sources":["../src/svelte/index.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"svelte.js","sources":["../src/svelte/index.ts"],"sourcesContent":["import { readable, derived } from 'svelte/store';\nimport type { Readable } from 'svelte/store';\nimport { Spotr } from '../Spotr';\nimport type { SpotrOptions, ExtractItemType } from '../types';\n\n// Overload: infer T from collection\nexport function createSpotr<\n C extends readonly object[] | object[] | Set<object>,\n>(\n options:\n | (Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C })\n | Readable<\n Omit<SpotrOptions<ExtractItemType<C>>, 'collection'> & { collection: C }\n >\n): Readable<Spotr<ExtractItemType<C> & object>>;\n// Overload: explicit generic\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | Readable<SpotrOptions<T>>\n): Readable<Spotr<T>>;\nexport function createSpotr<T extends object>(\n options: SpotrOptions<T> | Readable<SpotrOptions<T>>\n): Readable<Spotr<T>> {\n const hasSubscribe =\n options != null &&\n typeof (options as Readable<SpotrOptions<T>>).subscribe === 'function';\n\n if (hasSubscribe) {\n return derived(\n options as Readable<SpotrOptions<T>>,\n (opts) => new Spotr(opts)\n );\n }\n return readable(new Spotr(options as SpotrOptions<T>));\n}\n\nexport { Spotr } from '../Spotr';\nexport type {\n SpotrOptions,\n SpotrResult,\n ScoredResult,\n MatchedKeyword,\n} from '../types';\n"],"names":["createSpotr","options","derived","opts","Spotr","readable"],"mappings":";;AAmBO,SAASA,EACdC,GACoB;AAKpB,SAHEA,KAAW,QACX,OAAQA,EAAsC,aAAc,aAGrDC;AAAA,IACLD;AAAA,IACA,CAACE,MAAS,IAAIC,EAAMD,CAAI;AAAA,EAAA,IAGrBE,EAAS,IAAID,EAAMH,CAA0B,CAAC;AACvD;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function o(e,t){if(e===t)return!0;if(typeof e!="object"||typeof t!="object"||e===null||t===null)return!1;const l=Object.keys(e),n=Object.keys(t);if(l.length!==n.length)return!1;for(const r of l)if(e[r]!==t[r])return!1;return!0}exports.shallowEqual=o;
|
|
2
|
+
//# sourceMappingURL=shallowEqual.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shallowEqual.cjs","sources":["../../src/utils/shallowEqual.ts"],"sourcesContent":["export function shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (\n (a as Record<string, unknown>)[key] !==\n (b as Record<string, unknown>)[key]\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"names":["shallowEqual","a","b","keysA","keysB","key"],"mappings":"gFAAO,SAASA,EAAaC,EAAYC,EAAqB,CAC5D,GAAID,IAAMC,EAAG,MAAO,GACpB,GACE,OAAOD,GAAM,UACb,OAAOC,GAAM,UACbD,IAAM,MACNC,IAAM,KAEN,MAAO,GAGT,MAAMC,EAAQ,OAAO,KAAKF,CAAC,EACrBG,EAAQ,OAAO,KAAKF,CAAC,EAE3B,GAAIC,EAAM,SAAWC,EAAM,OAAQ,MAAO,GAE1C,UAAWC,KAAOF,EAChB,GACGF,EAA8BI,CAAG,IACjCH,EAA8BG,CAAG,EAElC,MAAO,GAIX,MAAO,EACT"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function l(e, t) {
|
|
2
|
+
if (e === t) return !0;
|
|
3
|
+
if (typeof e != "object" || typeof t != "object" || e === null || t === null)
|
|
4
|
+
return !1;
|
|
5
|
+
const n = Object.keys(e), f = Object.keys(t);
|
|
6
|
+
if (n.length !== f.length) return !1;
|
|
7
|
+
for (const r of n)
|
|
8
|
+
if (e[r] !== t[r])
|
|
9
|
+
return !1;
|
|
10
|
+
return !0;
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
l as shallowEqual
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=shallowEqual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shallowEqual.js","sources":["../../src/utils/shallowEqual.ts"],"sourcesContent":["export function shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (\n (a as Record<string, unknown>)[key] !==\n (b as Record<string, unknown>)[key]\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"names":["shallowEqual","a","b","keysA","keysB","key"],"mappings":"AAAO,SAASA,EAAaC,GAAYC,GAAqB;AAC5D,MAAID,MAAMC,EAAG,QAAO;AACpB,MACE,OAAOD,KAAM,YACb,OAAOC,KAAM,YACbD,MAAM,QACNC,MAAM;AAEN,WAAO;AAGT,QAAMC,IAAQ,OAAO,KAAKF,CAAC,GACrBG,IAAQ,OAAO,KAAKF,CAAC;AAE3B,MAAIC,EAAM,WAAWC,EAAM,OAAQ,QAAO;AAE1C,aAAWC,KAAOF;AAChB,QACGF,EAA8BI,CAAG,MACjCH,EAA8BG,CAAG;AAElC,aAAO;AAIX,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spotr",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"description": "A powerful
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
|
+
"description": "A powerful fuzzy search library for client-side collections in TypeScript",
|
|
5
5
|
"author": "Andy Merskin <andymerskin@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "https://github.com/andymerskin/spotr",
|