11import { z } from "zod" ;
2+ import {
3+ defineEventHandler ,
4+ getValidatedQuery ,
5+ setResponseHeader ,
6+ toWebRequest ,
7+ } from "h3" ;
8+ import { Readable } from "node:stream" ;
9+
10+ import { useBinding } from "../../../server/utils/bucket" ;
11+ import { usePackagesBucket } from "../../../server/utils/bucket" ;
212
313const querySchema = z . object ( {
414 text : z . string ( ) ,
@@ -20,53 +30,122 @@ export default defineEventHandler(async (event) => {
2030
2131 const searchText = query . text . toLowerCase ( ) ;
2232
23- // Internal pagination: iterate until uniqueNodes is filled or no more objects
24- let cursor : string | undefined ;
25- const seen = new Set < string > ( ) ;
26- const uniqueNodes = [ ] ;
27- const maxNodes = 10 ;
28- let keepGoing = true ;
29-
30- while ( uniqueNodes . length < maxNodes && keepGoing && ! signal . aborted ) {
31- const listResult = await r2Binding . list ( {
32- prefix : usePackagesBucket . base ,
33- limit : 1000 ,
34- cursor,
35- } ) ;
36- const { objects, truncated } = listResult ;
37- cursor = truncated ? listResult . cursor : undefined ;
38-
39- for ( const obj of objects ) {
40- const parts = parseKey ( obj . key ) ;
41- const orgRepo = `${ parts . org } /${ parts . repo } ` . toLowerCase ( ) ;
42- const applies =
43- parts . org . toLowerCase ( ) . includes ( searchText ) ||
44- parts . repo . toLowerCase ( ) . includes ( searchText ) ||
45- orgRepo . includes ( searchText ) ;
46- if ( ! applies ) continue ;
47-
48- const key = `${ parts . org } /${ parts . repo } ` ;
49- if ( ! seen . has ( key ) ) {
50- seen . add ( key ) ;
51- uniqueNodes . push ( {
52- name : parts . repo ,
53- owner : {
54- login : parts . org ,
55- avatarUrl : `https://github.com/${ parts . org } .png` ,
56- } ,
33+ setResponseHeader ( event , "Content-Type" , "application/json" ) ;
34+ setResponseHeader ( event , "Cache-Control" , "no-cache" ) ;
35+ setResponseHeader ( event , "Connection" , "keep-alive" ) ;
36+
37+ const stream = new Readable ( {
38+ objectMode : true ,
39+ read ( ) { } ,
40+ } ) ;
41+
42+ stream . push ( JSON . stringify ( { nodes : [ ] , streaming : true } ) + "\n" ) ;
43+
44+ processSearchAsync ( ) ;
45+
46+ return stream ;
47+
48+ async function processSearchAsync ( ) {
49+ try {
50+ let cursor : string | undefined ;
51+ const seen = new Set < string > ( ) ;
52+ const maxNodes = 10 ;
53+ let count = 0 ;
54+ let keepGoing = true ;
55+
56+ // Debug: Log the base prefix we're using to search
57+ console . log ( `Searching with base prefix: ${ usePackagesBucket . base } ` ) ;
58+
59+ while ( count < maxNodes && keepGoing && ! signal . aborted ) {
60+ const prefix = usePackagesBucket . base ;
61+
62+ console . log (
63+ `Fetching batch with prefix: ${ prefix } , cursor: ${ cursor || "initial" } ` ,
64+ ) ;
65+
66+ const listResult = await r2Binding . list ( {
67+ prefix : prefix ,
68+ limit : 1000 ,
69+ cursor,
5770 } ) ;
58- if ( uniqueNodes . length >= maxNodes ) break ;
71+
72+ console . log (
73+ `Fetched ${ listResult . objects . length } objects, truncated: ${ listResult . truncated } ` ,
74+ ) ;
75+
76+ const { objects, truncated } = listResult ;
77+ cursor = truncated ? listResult . cursor : undefined ;
78+
79+ const batchResults = [ ] ;
80+
81+ for ( const obj of objects ) {
82+ console . log ( `Examining key: ${ obj . key } ` ) ;
83+
84+ try {
85+ const parts = parseKey ( obj . key ) ;
86+
87+ if ( ! parts . org || ! parts . repo ) {
88+ console . log ( `Skipping malformed key: ${ obj . key } ` ) ;
89+ continue ;
90+ }
91+
92+ const orgRepo = `${ parts . org } /${ parts . repo } ` . toLowerCase ( ) ;
93+
94+ console . log ( `Matching ${ orgRepo } against search: ${ searchText } ` ) ;
95+
96+ const applies =
97+ parts . org . toLowerCase ( ) . includes ( searchText ) ||
98+ parts . repo . toLowerCase ( ) . includes ( searchText ) ||
99+ orgRepo . includes ( searchText ) ;
100+
101+ if ( ! applies ) continue ;
102+
103+ const key = `${ parts . org } /${ parts . repo } ` ;
104+ if ( ! seen . has ( key ) ) {
105+ seen . add ( key ) ;
106+ const node = {
107+ name : parts . repo ,
108+ owner : {
109+ login : parts . org ,
110+ avatarUrl : `https://github.com/${ parts . org } .png` ,
111+ } ,
112+ } ;
113+ batchResults . push ( node ) ;
114+ count ++ ;
115+ console . log ( `Found match: ${ key } ` ) ;
116+ if ( count >= maxNodes ) break ;
117+ }
118+ } catch ( err ) {
119+ console . error ( `Error parsing key ${ obj . key } :` , err ) ;
120+ continue ;
121+ }
122+ }
123+
124+ if ( batchResults . length > 0 ) {
125+ console . log ( `Streaming batch of ${ batchResults . length } results` ) ;
126+ stream . push (
127+ JSON . stringify ( { nodes : batchResults , streaming : true } ) + "\n" ,
128+ ) ;
129+ }
130+
131+ if ( ! truncated || count >= maxNodes ) {
132+ keepGoing = false ;
133+ }
59134 }
60- }
61135
62- if ( ! truncated || uniqueNodes . length >= maxNodes ) {
63- keepGoing = false ;
136+ console . log ( `Search complete, found ${ count } results` ) ;
137+ stream . push (
138+ JSON . stringify ( { streaming : false , complete : true } ) + "\n" ,
139+ ) ;
140+ stream . push ( null ) ;
141+ } catch ( error ) {
142+ console . error ( "Error processing search:" , error ) ;
143+ stream . push (
144+ JSON . stringify ( { error : true , message : ( error as Error ) . message } ) +
145+ "\n" ,
146+ ) ;
64147 }
65148 }
66-
67- return {
68- nodes : uniqueNodes ,
69- } ;
70149 } catch ( error ) {
71150 console . error ( "Error in repository search:" , error ) ;
72151 return {
@@ -78,9 +157,18 @@ export default defineEventHandler(async (event) => {
78157} ) ;
79158
80159function parseKey ( key : string ) {
81- const parts = key . split ( ":" ) ;
82- return {
83- org : parts [ 2 ] ,
84- repo : parts [ 3 ] ,
85- } ;
160+ try {
161+ const parts = key . split ( ":" ) ;
162+ if ( parts . length < 4 ) {
163+ console . warn ( `Key format unexpected: ${ key } , parts: ${ parts . length } ` ) ;
164+ return { org : "" , repo : "" } ;
165+ }
166+ return {
167+ org : parts [ 2 ] || "" ,
168+ repo : parts [ 3 ] || "" ,
169+ } ;
170+ } catch ( err ) {
171+ console . error ( `Failed to parse key: ${ key } ` , err ) ;
172+ return { org : "" , repo : "" } ;
173+ }
86174}
0 commit comments