11import { getTableOfContents , parseFrontmatter } from "../../utils/docs.ts" ;
2+ import {
3+ create ,
4+ insert ,
5+ insertMultiple ,
6+ search as oramaSearch ,
7+ } from "@orama/orama" ;
28
39interface SearchResult {
410 title : string ;
511 url : string ;
612 excerpt : string ;
713 type : "doc" | "api" | "example" | "blog" | "std" ;
8- // human-friendly label for UI badges (e.g. "Docs", "Std")
914 label : string ;
1015 score : number ;
1116 highlights : string [ ] ;
@@ -30,7 +35,30 @@ interface GitTreeItem {
3035let indexCache : SearchResult [ ] | null = null ;
3136let rawIndexCache : RawIndexItem [ ] | null = null ;
3237let lastIndexTime = 0 ;
33- const INDEX_CACHE_DURATION = 60 * 1000 ; // 60s
38+ const INDEX_CACHE_DURATION = 60 * 1000 ;
39+
40+ let oramaDb : ReturnType < typeof create > | null = null ;
41+
42+ function ensureOrama ( ) {
43+ if ( oramaDb ) return oramaDb ;
44+ try {
45+ oramaDb = create ( {
46+ schema : {
47+ title : "string" ,
48+ excerpt : "string" ,
49+ body : "string" ,
50+ type : "string" ,
51+ url : "string" ,
52+ label : "string" ,
53+ } ,
54+ } ) ;
55+ return oramaDb ;
56+ } catch ( e ) {
57+ console . warn ( "Orama init failed, falling back to built-in search:" , e ) ;
58+ oramaDb = null ;
59+ return null ;
60+ }
61+ }
3462
3563async function buildIndex ( ) : Promise < SearchResult [ ] > {
3664 const now = Date . now ( ) ;
@@ -173,6 +201,50 @@ async function buildIndex(): Promise<SearchResult[]> {
173201 rawIndexCache = items ;
174202 indexCache = results ;
175203 lastIndexTime = Date . now ( ) ;
204+
205+ // Attempt to index into Orama (best-effort). Use insertMultiple when possible.
206+ try {
207+ const db = ensureOrama ( ) ;
208+ if ( db ) {
209+ // Prepare docs for Orama inserting. Keep id as url to make it stable.
210+ const docs = items . map ( ( it ) => ( {
211+ id : it . url ,
212+ title : it . title ,
213+ excerpt : it . excerpt || "" ,
214+ body : it . body || "" ,
215+ type : it . type ,
216+ url : it . url ,
217+ label : ( ( ) => {
218+ switch ( it . type ) {
219+ case "api" :
220+ return "API" ;
221+ case "example" :
222+ return "Example" ;
223+ case "blog" :
224+ return "Blog" ;
225+ case "std" :
226+ return "Std" ;
227+ default :
228+ return "Docs" ;
229+ }
230+ } ) ( ) ,
231+ } ) ) ;
232+ if ( typeof insertMultiple === "function" ) {
233+ Promise . resolve ( insertMultiple ( db , docs ) )
234+ . then ( ( ) => { } )
235+ . catch ( ( e : Error ) => {
236+ console . warn ( "Orama insertMultiple failed:" , e ) ;
237+ } ) ;
238+ } else {
239+ for ( const d of docs ) {
240+ // @ts -ignore: best-effort insert, allow unknown signature
241+ insert ( db , d ) . catch ( ( ) => { } ) ;
242+ }
243+ }
244+ }
245+ } catch ( e ) {
246+ console . warn ( "Failed to populate Orama DB:" , e ) ;
247+ }
176248 return results ;
177249}
178250
@@ -232,7 +304,7 @@ function scoreAndFilter(results: SearchResult[], query: string, limit = 10) {
232304
233305 scored . sort ( ( a , b ) => b . score - a . score ) ;
234306 return scored . slice ( 0 , limit ) . map ( ( s ) => ( {
235- ...s . r ,
307+ ...s . r ,
236308 score : s . score ,
237309 highlights : s . highlights ,
238310 } ) ) ;
@@ -279,32 +351,90 @@ export const handler = {
279351 parseInt ( url . searchParams . get ( "pageSize" ) || String ( limit ) ) ,
280352 ) ;
281353
282- const scored = scoreAndFilter (
283- filtered . map ( ( it ) => ( {
284- title : it . title ,
285- url : it . url ,
286- excerpt : it . excerpt || "" ,
287- type : it . type ,
288- label : ( ( ) => {
289- switch ( it . type ) {
290- case "api" :
291- return "API" ;
292- case "example" :
293- return "Example" ;
294- case "blog" :
295- return "Blog" ;
296- case "std" :
297- return "Std" ;
298- default :
299- return "Docs" ;
354+ const q = query as string ;
355+
356+ let scored : SearchResult [ ] = [ ] ;
357+ try {
358+ const db = oramaDb || ensureOrama ( ) ;
359+ if ( db ) {
360+ try {
361+ // @ts -ignore: imported from npm specifier in Deno environment
362+ const oramaRes = await oramaSearch ( db , { term : query } ) ;
363+ if (
364+ oramaRes &&
365+ Array . isArray ( ( oramaRes as unknown as { hits ?: unknown } ) . hits )
366+ ) {
367+ const hits =
368+ ( oramaRes as unknown as { hits : Array < unknown > } ) . hits ;
369+ const mapped : SearchResult [ ] = hits . map ( ( h ) => {
370+ const hit = h as unknown as {
371+ id ?: string ;
372+ score ?: number ;
373+ document ?: Record < string , unknown > ;
374+ } ;
375+ const doc = hit . document || { } ;
376+ const title = ( doc . title as string ) || ( doc . name as string ) ||
377+ "" ;
378+ const urlVal = ( doc . url as string ) || hit . id || "" ;
379+ const excerpt = ( doc . excerpt as string ) || "" ;
380+ const typeVal = ( doc . type as string ) ||
381+ ( doc . label as string ) || "doc" ;
382+ const labelVal = ( doc . label as string ) || "Docs" ;
383+ return {
384+ title,
385+ url : urlVal ,
386+ excerpt,
387+ type : ( typeVal as string ) as SearchResult [ "type" ] ,
388+ label : labelVal ,
389+ score : typeof hit . score === "number" ? hit . score : 0 ,
390+ highlights : [ ] ,
391+ } ;
392+ } ) ;
393+
394+ const filteredHits = allowedTypes
395+ ? mapped . filter ( ( m ) => allowedTypes . includes ( m . type ) )
396+ : mapped ;
397+ scored = filteredHits ;
300398 }
301- } ) ( ) ,
302- score : 0 ,
303- highlights : [ ] ,
304- } ) ) ,
305- query ,
306- filtered . length ,
307- ) ;
399+ } catch ( e ) {
400+ console . warn ( "Orama search call failed:" , e ) ;
401+ }
402+ }
403+ } catch ( e ) {
404+ console . warn (
405+ "Orama search failed, falling back to legacy scorer:" ,
406+ e ,
407+ ) ;
408+ }
409+
410+ if ( ! scored || scored . length === 0 ) {
411+ scored = scoreAndFilter (
412+ filtered . map ( ( it ) => ( {
413+ title : it . title ,
414+ url : it . url ,
415+ excerpt : it . excerpt || "" ,
416+ type : it . type ,
417+ label : ( ( ) => {
418+ switch ( it . type ) {
419+ case "api" :
420+ return "API" ;
421+ case "example" :
422+ return "Example" ;
423+ case "blog" :
424+ return "Blog" ;
425+ case "std" :
426+ return "Std" ;
427+ default :
428+ return "Docs" ;
429+ }
430+ } ) ( ) ,
431+ score : 0 ,
432+ highlights : [ ] ,
433+ } ) ) ,
434+ q ,
435+ filtered . length ,
436+ ) ;
437+ }
308438
309439 const total = scored . length ;
310440 const start = ( page - 1 ) * pageSize ;
0 commit comments