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 ) {
122 return String ( q || '' )
133 . trim ( )
144 . toLowerCase ( )
15- . split ( / [ \s # . / _ \ -] + / )
5+ . split ( / [ \s # . / _ - ] + / )
166 . filter ( Boolean ) ;
177}
188
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- */
259function parseSearchQuery ( raw ) {
2610 const text = String ( raw || '' ) . trim ( ) ;
2711 const filters = {
@@ -207,44 +191,24 @@ function expandQueryVariants(rawQ, settings = {}) {
207191 return Array . from ( set ) ;
208192}
209193
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- */
215-
216- /** @type {Record<string, import('./types.js').SearchProfileConfig> } */
217194const PROFILE_SETTINGS = {
218195 legacy : { fuzzyCutoff : 0.4 , scoreScale : 1 , maxCandidates : 8000 } ,
219196 balanced : { fuzzyCutoff : 0.48 , scoreScale : 1.08 , maxCandidates : 4000 } ,
220197 'max-quality' : { fuzzyCutoff : 0.35 , scoreScale : 1.16 , maxCandidates : 9000 } ,
221198} ;
222199
223- /**
224- * Resolve a profile name to its config (falls back to `legacy`).
225- * @param {string } [profile]
226- * @returns {import('./types.js').SearchProfileConfig }
227- */
228200function getProfileConfig ( profile ) {
229201 const key = String ( profile || 'legacy' ) . toLowerCase ( ) ;
230202 return PROFILE_SETTINGS [ key ] || PROFILE_SETTINGS . legacy ;
231203}
232204
233- /** Stable list of known profile ids. */
234205const PROFILE_IDS = Object . freeze ( [ 'legacy' , 'balanced' , 'max-quality' ] ) ;
235206
236- /**
237- * Glyph Search core: ranking, snippets, index + engine factory.
238- * @module engine
239- */
240-
241207
242208
243209
244210
245- /** @type {Record<string, number> } */
246211const CAT_PRIORITY = { page : 40 , note : 36 , app : 32 , release : 30 , action : 24 , news : 20 } ;
247- /** @type {import('./types.js').SearchSettings } */
248212const SEARCH_SETTINGS = { fuzzyLayout : true , fuzzyTransliteration : true } ;
249213const TOKEN_VARIANT_CACHE = new Map ( ) ;
250214const SNIPPET_CACHE = new Map ( ) ;
@@ -286,11 +250,6 @@ function getTokenVariantsCached(tok, settings) {
286250 return variants ;
287251}
288252
289- /**
290- * @param {import('./types.js').SearchItem } it
291- * @param {import('./types.js').ParsedSearchQuery|null|undefined } filters
292- * @returns {boolean }
293- */
294253function matchesSearchFilters ( it , filters ) {
295254 if ( ! filters ) return true ;
296255 if ( filters . type === 'release' && it . cat !== 'release' ) return false ;
@@ -319,14 +278,6 @@ function matchesSearchFilters(it, filters) {
319278 return true ;
320279}
321280
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- */
330281function scoreSearchItem ( it , tokens , filters , settings = SEARCH_SETTINGS ) {
331282 if ( filters && ! matchesSearchFilters ( it , filters ) ) return 0 ;
332283 const title = it . title ( ) . toLowerCase ( ) ;
@@ -424,14 +375,6 @@ function findSnippetInBlob(blob, tok, settings) {
424375 return null ;
425376}
426377
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- */
435378function snippetForItem ( it , tokens , esc = ( s ) => s , settings = SEARCH_SETTINGS ) {
436379 if ( ! tokens . length ) return '' ;
437380 const body = typeof it . body === 'function' ? it . body ( ) : it . body || '' ;
@@ -464,10 +407,6 @@ function itemBodyText(it) {
464407 return String ( it . body || '' ) ;
465408}
466409
467- /**
468- * Cheap haystack for fast-path gating. MUST include body — otherwise
469- * paragraph-only hits never reach scoreSearchItem (full-text search broken).
470- */
471410function textBagForItem ( it ) {
472411 const title = String ( it . title ?. ( ) || '' ) . toLowerCase ( ) ;
473412 const sub = String ( it . sub || '' ) . toLowerCase ( ) ;
@@ -517,13 +456,6 @@ function collectTopK(scored, limit) {
517456 return top . sort ( ( a , b ) => b . score - a . score ) ;
518457}
519458
520- /**
521- * Rank corpus items for a query string.
522- * @param {import('./types.js').SearchItem[] } items
523- * @param {string } q
524- * @param {import('./types.js').RankSearchOptions } [opts]
525- * @returns {import('./types.js').RankedHit[] }
526- */
527459function rankSearchItems ( items , q , opts = { } ) {
528460 const settings = { ...SEARCH_SETTINGS , ...( opts . settings || { } ) } ;
529461 settings . profile = settings . profile || opts . profile || 'legacy' ;
@@ -533,7 +465,6 @@ function rankSearchItems(items, q, opts = {}) {
533465 const tokens = filters . tokens . length ? filters . tokens : tokenizeQuery ( q ) ;
534466 const limit = opts . limit ?? 12 ;
535467
536- // Prefer precomputed bags from buildIndex / createSearchEngine when provided.
537468 const indexRows = Array . isArray ( opts . index ?. items ) ? opts . index . items : null ;
538469 const sourceLen = indexRows ? indexRows . length : items . length ;
539470 const candidates = [ ] ;
@@ -567,12 +498,6 @@ function rankSearchItems(items, q, opts = {}) {
567498 return out ;
568499}
569500
570- /**
571- * Pre-compute text bags for repeated searches.
572- * @param {import('./types.js').SearchItem[] } [items]
573- * @param {import('./types.js').BuildIndexOptions } [opts]
574- * @returns {import('./types.js').SearchIndex }
575- */
576501function buildIndex ( items = [ ] , opts = { } ) {
577502 const profile = opts . profile || 'legacy' ;
578503 const index = items . map ( ( it , idx ) => ( {
@@ -584,11 +509,6 @@ function buildIndex(items = [], opts = {}) {
584509 return { items : index , profile, createdAt : Date . now ( ) } ;
585510}
586511
587- /**
588- * Create a reusable search engine bound to an index / items.
589- * @param {import('./types.js').CreateSearchEngineOptions } [options]
590- * @returns {import('./types.js').SearchEngine }
591- */
592512function createSearchEngine ( options = { } ) {
593513 const profile = options . profile || 'balanced' ;
594514 const settings = { ...SEARCH_SETTINGS , ...( options . settings || { } ) , profile } ;
0 commit comments