@@ -7,66 +7,215 @@ const querySchema = z.object({
77 text : z . string ( ) ,
88} ) ;
99
10+ interface SearchDebugInfo {
11+ startTime : string ;
12+ endTime : string ;
13+ totalElapsedMs : number ;
14+ processedRepositories : number ;
15+ matchesFound : number ;
16+ averageProcessingTimePerRepo : number ;
17+ repositoriesPerSecond : number ;
18+ searchQuery : string ;
19+ status : "completed" | "aborted" | "error" ;
20+ flowErrors : Array < {
21+ stage : string ;
22+ error : string ;
23+ timestamp : string ;
24+ repositoryContext ?: string ;
25+ } > ;
26+ flowStages : Array < {
27+ stage : string ;
28+ timestamp : string ;
29+ details ?: string ;
30+ } > ;
31+ }
32+
1033export default defineEventHandler ( async ( event ) => {
1134 const request = toWebRequest ( event ) ;
1235 const signal = request . signal ;
1336
37+ const flowErrors : SearchDebugInfo [ "flowErrors" ] = [ ] ;
38+ const flowStages : SearchDebugInfo [ "flowStages" ] = [ ] ;
39+
40+ const addFlowStage = ( stage : string , details ?: string ) => {
41+ flowStages . push ( {
42+ stage,
43+ timestamp : new Date ( ) . toISOString ( ) ,
44+ details,
45+ } ) ;
46+ } ;
47+
48+ const addFlowError = (
49+ stage : string ,
50+ error : string ,
51+ repositoryContext ?: string ,
52+ ) => {
53+ flowErrors . push ( {
54+ stage,
55+ error,
56+ timestamp : new Date ( ) . toISOString ( ) ,
57+ repositoryContext,
58+ } ) ;
59+ } ;
60+
1461 try {
62+ addFlowStage ( "query_validation" , "Starting query validation" ) ;
63+
1564 const query = await getValidatedQuery ( event , ( data ) =>
1665 querySchema . parse ( data ) ,
1766 ) ;
18- if ( ! query . text ) return { nodes : [ ] } ;
1967
20- const app = useOctokitApp ( event ) ;
68+ if ( ! query . text ) {
69+ addFlowStage ( "early_return" , "Empty query text" ) ;
70+ return { nodes : [ ] , debug : null } ;
71+ }
72+
73+ addFlowStage ( "query_validated" , `Query: "${ query . text } "` ) ;
74+
75+ let app ;
76+ try {
77+ addFlowStage ( "octokit_init" , "Initializing Octokit app" ) ;
78+ app = useOctokitApp ( event ) ;
79+ addFlowStage ( "octokit_ready" , "Octokit app initialized successfully" ) ;
80+ } catch ( err : any ) {
81+ addFlowError ( "octokit_init" , err . message ) ;
82+ throw new Error ( `Failed to initialize Octokit: ${ err . message } ` ) ;
83+ }
84+
2185 const searchText = query . text . toLowerCase ( ) ;
2286 const matches : RepoNode [ ] = [ ] ;
87+ const startTime = Date . now ( ) ;
88+ let processedRepositories = 0 ;
89+ let status : SearchDebugInfo [ "status" ] = "completed" ;
90+ let skippedRepositories = 0 ;
91+ let suspendedErrors = 0 ;
2392
24- await app . eachInstallation ( async ( { octokit, installation } ) => {
25- if ( signal . aborted ) return ;
26-
27- if ( installation . suspended_at ) {
28- console . warn ( `Skipping suspended installation ${ installation . id } ` ) ;
29- return ;
30- }
93+ addFlowStage (
94+ "repository_iteration_start" ,
95+ `Starting to iterate repositories for search: "${ searchText } "` ,
96+ ) ;
97+
98+ try {
99+ await app . eachRepository ( async ( { repository } ) => {
100+ try {
101+ if ( signal . aborted ) {
102+ addFlowStage (
103+ "search_aborted" ,
104+ `Aborted at repository: ${ repository . full_name } ` ,
105+ ) ;
106+ status = "aborted" ;
107+ return ;
108+ }
31109
32- try {
33- const repos = await octokit . paginate ( "GET /installation/repositories" ) ;
110+ if ( repository . private ) {
111+ skippedRepositories ++ ;
112+ return ;
113+ }
34114
35- for ( const repository of repos ) {
36- if ( signal . aborted ) return ;
37- if ( repository . private ) continue ;
115+ processedRepositories ++ ;
116+
117+ // Add periodic progress tracking
118+ if ( processedRepositories % 100 === 0 ) {
119+ const elapsed = Date . now ( ) - startTime ;
120+ addFlowStage (
121+ "progress_checkpoint" ,
122+ `Processed ${ processedRepositories } repositories in ${ elapsed } ms` ,
123+ ) ;
124+ }
38125
39126 const repoName = repository . name . toLowerCase ( ) ;
40127 const ownerLogin = repository . owner . login . toLowerCase ( ) ;
41128
42- const nameScore = stringSimilarity . compareTwoStrings (
43- repoName ,
44- searchText ,
45- ) ;
46- const ownerScore = stringSimilarity . compareTwoStrings (
47- ownerLogin ,
48- searchText ,
49- ) ;
129+ let nameScore , ownerScore ;
130+ try {
131+ nameScore = stringSimilarity . compareTwoStrings (
132+ repoName ,
133+ searchText ,
134+ ) ;
135+ ownerScore = stringSimilarity . compareTwoStrings (
136+ ownerLogin ,
137+ searchText ,
138+ ) ;
139+ } catch ( err : any ) {
140+ addFlowError (
141+ "string_similarity" ,
142+ err . message ,
143+ repository . full_name ,
144+ ) ;
145+ // Use fallback scoring
146+ nameScore = repoName . includes ( searchText ) ? 0.5 : 0 ;
147+ ownerScore = ownerLogin . includes ( searchText ) ? 0.5 : 0 ;
148+ }
50149
51- matches . push ( {
52- id : repository . id ,
53- name : repository . name ,
54- owner : {
55- login : repository . owner . login ,
56- avatarUrl : repository . owner . avatar_url ,
57- } ,
58- stars : repository . stargazers_count || 0 ,
59- score : Math . max ( nameScore , ownerScore ) ,
60- } ) ;
150+ try {
151+ matches . push ( {
152+ id : repository . id ,
153+ name : repository . name ,
154+ owner : {
155+ login : repository . owner . login ,
156+ avatarUrl : repository . owner . avatar_url ,
157+ } ,
158+ stars : repository . stargazers_count || 0 ,
159+ score : Math . max ( nameScore , ownerScore ) ,
160+ } ) ;
161+ } catch ( err : any ) {
162+ addFlowError ( "match_creation" , err . message , repository . full_name ) ;
163+ }
164+ } catch ( err : any ) {
165+ if (
166+ err . message ?. includes ( "suspended" ) ||
167+ err . message ?. includes ( "Installation" )
168+ ) {
169+ suspendedErrors ++ ;
170+ addFlowError (
171+ "repository_suspended" ,
172+ err . message ,
173+ repository . full_name ,
174+ ) ;
175+ return ;
176+ }
177+ addFlowError (
178+ "repository_processing" ,
179+ err . message ,
180+ repository . full_name ,
181+ ) ;
182+ throw err ;
61183 }
62- } catch ( error ) {
63- console . warn ( `Error fetching repositories for installation ${ installation . id } :` , error ) ;
184+ } ) ;
185+
186+ addFlowStage (
187+ "repository_iteration_complete" ,
188+ `Completed repository iteration` ,
189+ ) ;
190+ } catch ( err : any ) {
191+ if (
192+ err . message ?. includes ( "suspended" ) ||
193+ err . message ?. includes ( "Installation" )
194+ ) {
195+ addFlowError (
196+ "iteration_suspended" ,
197+ `Installation suspended after processing ${ processedRepositories } repositories: ${ err . message } ` ,
198+ ) ;
199+ status = "completed" ;
200+ } else {
201+ addFlowError ( "iteration_failed" , err . message ) ;
202+ status = "error" ;
203+ throw err ;
64204 }
65- } ) ;
205+ }
66206
67- matches . sort ( ( a , b ) =>
68- b . score !== a . score ? b . score - a . score : b . stars - a . stars ,
69- ) ;
207+ const totalElapsed = Date . now ( ) - startTime ;
208+
209+ addFlowStage ( "sorting_matches" , `Sorting ${ matches . length } matches` ) ;
210+
211+ try {
212+ matches . sort ( ( a , b ) =>
213+ b . score !== a . score ? b . score - a . score : b . stars - a . stars ,
214+ ) ;
215+ addFlowStage ( "sorting_complete" , "Matches sorted successfully" ) ;
216+ } catch ( err : any ) {
217+ addFlowError ( "sorting" , err . message ) ;
218+ }
70219
71220 const top = matches . slice ( 0 , 10 ) . map ( ( node ) => ( {
72221 id : node . id ,
@@ -75,8 +224,44 @@ export default defineEventHandler(async (event) => {
75224 stars : node . stars ,
76225 } ) ) ;
77226
78- return { nodes : top } ;
227+ addFlowStage ( "response_preparation" , `Prepared ${ top . length } top results` ) ;
228+
229+ const debugInfo : SearchDebugInfo = {
230+ startTime : new Date ( startTime ) . toISOString ( ) ,
231+ endTime : new Date ( ) . toISOString ( ) ,
232+ totalElapsedMs : totalElapsed ,
233+ processedRepositories,
234+ matchesFound : matches . length ,
235+ averageProcessingTimePerRepo :
236+ processedRepositories > 0 ? totalElapsed / processedRepositories : 0 ,
237+ repositoriesPerSecond : processedRepositories / ( totalElapsed / 1000 ) ,
238+ searchQuery : query . text ,
239+ status,
240+ flowErrors,
241+ flowStages,
242+ } ;
243+
244+ return { nodes : top , debug : debugInfo } ;
79245 } catch ( error ) {
80- return { nodes : [ ] , error : true , message : ( error as Error ) . message } ;
246+ addFlowError ( "global_error" , ( error as Error ) . message ) ;
247+
248+ return {
249+ nodes : [ ] ,
250+ error : true ,
251+ message : ( error as Error ) . message ,
252+ debug : {
253+ startTime : new Date ( ) . toISOString ( ) ,
254+ endTime : new Date ( ) . toISOString ( ) ,
255+ totalElapsedMs : 0 ,
256+ processedRepositories : 0 ,
257+ matchesFound : 0 ,
258+ averageProcessingTimePerRepo : 0 ,
259+ repositoriesPerSecond : 0 ,
260+ searchQuery : "" ,
261+ status : "error" as const ,
262+ flowErrors,
263+ flowStages,
264+ } ,
265+ } ;
81266 }
82267} ) ;
0 commit comments