1+ /**
2+ * Query tokenization and extended search grammar.
3+ * @module tokenize
4+ */
5+
6+ /**
7+ * Split a query into lowercase tokens on whitespace / punctuation.
8+ * @param {string } q
9+ * @returns {string[] }
10+ */
111function tokenizeQuery ( q ) {
212 return String ( q || '' )
313 . trim ( )
@@ -6,6 +16,12 @@ function tokenizeQuery(q) {
616 . filter ( Boolean ) ;
717}
818
19+ /**
20+ * Parse extended query grammar into tokens + structured filters.
21+ * Supports phrases, exclusions, OR groups, and type/page/app/path/tag filters.
22+ * @param {string } raw
23+ * @returns {import('./types.js').ParsedSearchQuery }
24+ */
925function parseSearchQuery ( raw ) {
1026 const text = String ( raw || '' ) . trim ( ) ;
1127 const filters = {
@@ -191,16 +207,45 @@ function expandQueryVariants(rawQ, settings = {}) {
191207 return Array . from ( set ) ;
192208}
193209
210+ /**
211+ * Search quality/speed profiles for glyph-s.
212+ * Data lives in profiles.json; this module re-exports for ESM and CJS bundling.
213+ * @module profiles
214+ */
194215
195-
196-
197- const CAT_PRIORITY = { page : 40 , note : 36 , app : 32 , release : 30 , action : 24 , news : 20 } ;
198- const SEARCH_SETTINGS = { fuzzyLayout : true , fuzzyTransliteration : true } ;
216+ /** @type {Record<string, import('./types.js').SearchProfileConfig> } */
199217const PROFILE_SETTINGS = {
200218 legacy : { fuzzyCutoff : 0.4 , scoreScale : 1 , maxCandidates : 8000 } ,
201219 balanced : { fuzzyCutoff : 0.48 , scoreScale : 1.08 , maxCandidates : 4000 } ,
202220 'max-quality' : { fuzzyCutoff : 0.35 , scoreScale : 1.16 , maxCandidates : 9000 } ,
203221} ;
222+
223+ /**
224+ * Resolve a profile name to its config (falls back to `legacy`).
225+ * @param {string } [profile]
226+ * @returns {import('./types.js').SearchProfileConfig }
227+ */
228+ function getProfileConfig ( profile ) {
229+ const key = String ( profile || 'legacy' ) . toLowerCase ( ) ;
230+ return PROFILE_SETTINGS [ key ] || PROFILE_SETTINGS . legacy ;
231+ }
232+
233+ /** Stable list of known profile ids. */
234+ const PROFILE_IDS = Object . freeze ( [ 'legacy' , 'balanced' , 'max-quality' ] ) ;
235+
236+ /**
237+ * Glyph Search core: ranking, snippets, index + engine factory.
238+ * @module engine
239+ */
240+
241+
242+
243+
244+
245+ /** @type {Record<string, number> } */
246+ const CAT_PRIORITY = { page : 40 , note : 36 , app : 32 , release : 30 , action : 24 , news : 20 } ;
247+ /** @type {import('./types.js').SearchSettings } */
248+ const SEARCH_SETTINGS = { fuzzyLayout : true , fuzzyTransliteration : true } ;
204249const TOKEN_VARIANT_CACHE = new Map ( ) ;
205250const SNIPPET_CACHE = new Map ( ) ;
206251
@@ -231,11 +276,6 @@ function tokenHitsText(tok, text, settings) {
231276 return null ;
232277}
233278
234- function getProfileConfig ( profile ) {
235- const key = String ( profile || 'legacy' ) . toLowerCase ( ) ;
236- return PROFILE_SETTINGS [ key ] || PROFILE_SETTINGS . legacy ;
237- }
238-
239279function getTokenVariantsCached ( tok , settings ) {
240280 const profile = String ( ( settings && settings . profile ) || 'legacy' ) ;
241281 const cacheKey = `${ profile } |${ String ( tok || '' ) . toLowerCase ( ) } |${ settings ?. fuzzyLayout !== false } |${ settings ?. fuzzyTransliteration !== false } ` ;
@@ -246,6 +286,11 @@ function getTokenVariantsCached(tok, settings) {
246286 return variants ;
247287}
248288
289+ /**
290+ * @param {import('./types.js').SearchItem } it
291+ * @param {import('./types.js').ParsedSearchQuery|null|undefined } filters
292+ * @returns {boolean }
293+ */
249294function matchesSearchFilters ( it , filters ) {
250295 if ( ! filters ) return true ;
251296 if ( filters . type === 'release' && it . cat !== 'release' ) return false ;
@@ -274,6 +319,14 @@ function matchesSearchFilters(it, filters) {
274319 return true ;
275320}
276321
322+ /**
323+ * Score one item against query tokens / filters.
324+ * @param {import('./types.js').SearchItem } it
325+ * @param {string[] } tokens
326+ * @param {import('./types.js').ParsedSearchQuery|null|undefined } filters
327+ * @param {import('./types.js').SearchSettings } [settings]
328+ * @returns {number }
329+ */
277330function scoreSearchItem ( it , tokens , filters , settings = SEARCH_SETTINGS ) {
278331 if ( filters && ! matchesSearchFilters ( it , filters ) ) return 0 ;
279332 const title = it . title ( ) . toLowerCase ( ) ;
@@ -371,6 +424,14 @@ function findSnippetInBlob(blob, tok, settings) {
371424 return null ;
372425}
373426
427+ /**
428+ * Build a short HTML-friendly snippet for the first matching token.
429+ * @param {import('./types.js').SearchItem } it
430+ * @param {string[] } tokens
431+ * @param {(s: string) => string } [esc]
432+ * @param {import('./types.js').SearchSettings } [settings]
433+ * @returns {string }
434+ */
374435function snippetForItem ( it , tokens , esc = ( s ) => s , settings = SEARCH_SETTINGS ) {
375436 if ( ! tokens . length ) return '' ;
376437 const body = typeof it . body === 'function' ? it . body ( ) : it . body || '' ;
@@ -441,6 +502,13 @@ function collectTopK(scored, limit) {
441502 return top . sort ( ( a , b ) => b . score - a . score ) ;
442503}
443504
505+ /**
506+ * Rank corpus items for a query string.
507+ * @param {import('./types.js').SearchItem[] } items
508+ * @param {string } q
509+ * @param {import('./types.js').RankSearchOptions } [opts]
510+ * @returns {import('./types.js').RankedHit[] }
511+ */
444512function rankSearchItems ( items , q , opts = { } ) {
445513 const settings = { ...SEARCH_SETTINGS , ...( opts . settings || { } ) } ;
446514 settings . profile = settings . profile || opts . profile || 'legacy' ;
@@ -479,6 +547,12 @@ function rankSearchItems(items, q, opts = {}) {
479547 return out ;
480548}
481549
550+ /**
551+ * Pre-compute text bags for repeated searches.
552+ * @param {import('./types.js').SearchItem[] } [items]
553+ * @param {import('./types.js').BuildIndexOptions } [opts]
554+ * @returns {import('./types.js').SearchIndex }
555+ */
482556function buildIndex ( items = [ ] , opts = { } ) {
483557 const profile = opts . profile || 'legacy' ;
484558 const index = items . map ( ( it , idx ) => ( {
@@ -490,6 +564,11 @@ function buildIndex(items = [], opts = {}) {
490564 return { items : index , profile, createdAt : Date . now ( ) } ;
491565}
492566
567+ /**
568+ * Create a reusable search engine bound to an index / items.
569+ * @param {import('./types.js').CreateSearchEngineOptions } [options]
570+ * @returns {import('./types.js').SearchEngine }
571+ */
493572function createSearchEngine ( options = { } ) {
494573 const profile = options . profile || 'balanced' ;
495574 const settings = { ...SEARCH_SETTINGS , ...( options . settings || { } ) , profile } ;
@@ -518,6 +597,9 @@ module.exports = {
518597 parseSearchQuery,
519598 expandTokenVariants,
520599 expandQueryVariants,
600+ getProfileConfig,
601+ PROFILE_SETTINGS ,
602+ PROFILE_IDS ,
521603 matchesSearchFilters,
522604 scoreSearchItem,
523605 snippetForItem,
0 commit comments