@@ -18,7 +18,8 @@ import type { FastifyInstance } from 'fastify';
1818import { z } from 'zod' ;
1919import path from 'node:path' ;
2020import type { StorageProvider } from '../../store/provider.js' ;
21- import type { CodeGraph } from '../../core/graph.js' ;
21+ import { IndexQueueFullError , MemoryPressureError } from '../../core/errors.js' ;
22+ import type { CodeGraphApi } from '../../core/graph.js' ;
2223import type { WebSocketBroadcaster } from '../websocket.js' ;
2324import { WebSocketEvents } from '../websocket.js' ;
2425import { isSubpath } from '../../core/path-utils.js' ;
@@ -30,12 +31,13 @@ const IndexRepositorySchema = z.object({
3031
3132export interface RepositoryRouteOptions {
3233 storage : StorageProvider ;
33- graph : CodeGraph ;
34+ graph : CodeGraphApi ;
3435 broadcaster : WebSocketBroadcaster ;
3536 workspaceRoot : string ;
3637 indexer ?: any ;
3738 watcher ?: any ;
3839 vectorStore ?: any ;
40+ indexQueue ?: any ;
3941}
4042
4143/**
@@ -115,34 +117,69 @@ export async function registerRepositoryRoutes(
115117 status : 'starting' ,
116118 } ) ;
117119
118- // Run indexing in background
119- setImmediate ( async ( ) => {
120- try {
121- const job = await options . indexer . indexRepository ( absolutePath , {
120+ // Run indexing in background, but handle queue admission errors immediately
121+ try {
122+ const indexingPromise = options . indexQueue ?. run ( ( ) =>
123+ options . indexer . indexRepository ( absolutePath , {
122124 incremental : input . incremental ,
123125 respectIgnore : true ,
124- } ) ;
126+ } )
127+ ) ?? options . indexer . indexRepository ( absolutePath , {
128+ incremental : input . incremental ,
129+ respectIgnore : true ,
130+ } ) ;
125131
126- options . broadcaster . broadcast ( WebSocketEvents . INDEX_COMPLETE , {
127- repositoryId : job . repositoryId ,
128- path : input . path ,
129- filesProcessed : job . filesProcessed ,
130- nodesCreated : job . nodesCreated ,
131- edgesCreated : job . edgesCreated ,
132- } ) ;
133- } catch ( error ) {
134- options . broadcaster . broadcast ( WebSocketEvents . INDEX_ERROR , {
135- path : input . path ,
136- error : error instanceof Error ? error . message : 'Unknown error' ,
137- } ) ;
138- }
139- } ) ;
132+ // Handle completion in background
133+ setImmediate ( async ( ) => {
134+ try {
135+ const job = await indexingPromise ;
136+ options . broadcaster . broadcast ( WebSocketEvents . INDEX_COMPLETE , {
137+ repositoryId : job . repositoryId ,
138+ path : input . path ,
139+ filesProcessed : job . filesProcessed ,
140+ nodesCreated : job . nodesCreated ,
141+ edgesCreated : job . edgesCreated ,
142+ } ) ;
143+ } catch ( error ) {
144+ options . broadcaster . broadcast ( WebSocketEvents . INDEX_ERROR , {
145+ path : input . path ,
146+ error : error instanceof Error ? error . message : 'Unknown error' ,
147+ } ) ;
148+ }
149+ } ) ;
140150
141- return {
142- success : true ,
143- path : input . path ,
144- message : 'Indexing started' ,
145- } ;
151+ return {
152+ success : true ,
153+ path : input . path ,
154+ message : 'Indexing started' ,
155+ } ;
156+ } catch ( error ) {
157+ if ( error instanceof IndexQueueFullError ) {
158+ return reply
159+ . status ( 429 )
160+ . header ( 'Retry-After' , String ( error . retryAfterSeconds ) )
161+ . send ( {
162+ error : {
163+ code : error . code ,
164+ message : 'indexing busy, retry later' ,
165+ retryAfterSeconds : error . retryAfterSeconds ,
166+ } ,
167+ } ) ;
168+ }
169+ if ( error instanceof MemoryPressureError ) {
170+ return reply
171+ . status ( 503 )
172+ . header ( 'Retry-After' , String ( error . retryAfterSeconds ) )
173+ . send ( {
174+ error : {
175+ code : error . code ,
176+ message : 'server under memory pressure, retry later' ,
177+ retryAfterSeconds : error . retryAfterSeconds ,
178+ } ,
179+ } ) ;
180+ }
181+ throw error ; // Re-throw other errors to be handled by outer catch
182+ }
146183 } catch ( error ) {
147184 return reply . status ( 500 ) . send ( {
148185 error : 'Indexing failed' ,
@@ -247,13 +284,22 @@ export async function registerRepositoryRoutes(
247284 status : 'reindexing' ,
248285 } ) ;
249286
250- // Run re-indexing in background
251- setImmediate ( async ( ) => {
252- try {
253- const job = await options . indexer . indexRepository ( repo . path , {
287+ // Run re-indexing in background, but handle queue admission errors immediately
288+ try {
289+ const indexingPromise = options . indexQueue ?. run ( ( ) =>
290+ options . indexer . indexRepository ( repo . path , {
254291 incremental : false ,
255292 respectIgnore : true ,
256- } ) ;
293+ } )
294+ ) ?? options . indexer . indexRepository ( repo . path , {
295+ incremental : false ,
296+ respectIgnore : true ,
297+ } ) ;
298+
299+ // Handle completion in background
300+ setImmediate ( async ( ) => {
301+ try {
302+ const job = await indexingPromise ;
257303
258304 options . broadcaster . broadcast ( WebSocketEvents . INDEX_COMPLETE , {
259305 repositoryId : job . repositoryId ,
@@ -270,11 +316,38 @@ export async function registerRepositoryRoutes(
270316 }
271317 } ) ;
272318
273- return {
274- success : true ,
275- repositoryId : id ,
276- message : 'Re-indexing started' ,
277- } ;
319+ return {
320+ success : true ,
321+ repositoryId : id ,
322+ message : 'Re-indexing started' ,
323+ } ;
324+ } catch ( error ) {
325+ if ( error instanceof IndexQueueFullError ) {
326+ return reply
327+ . status ( 429 )
328+ . header ( 'Retry-After' , String ( error . retryAfterSeconds ) )
329+ . send ( {
330+ error : {
331+ code : error . code ,
332+ message : 'indexing busy, retry later' ,
333+ retryAfterSeconds : error . retryAfterSeconds ,
334+ } ,
335+ } ) ;
336+ }
337+ if ( error instanceof MemoryPressureError ) {
338+ return reply
339+ . status ( 503 )
340+ . header ( 'Retry-After' , String ( error . retryAfterSeconds ) )
341+ . send ( {
342+ error : {
343+ code : error . code ,
344+ message : 'server under memory pressure, retry later' ,
345+ retryAfterSeconds : error . retryAfterSeconds ,
346+ } ,
347+ } ) ;
348+ }
349+ throw error ; // Re-throw other errors to be handled by outer catch
350+ }
278351 } catch ( error ) {
279352 return reply . status ( 500 ) . send ( {
280353 error : 'Re-indexing failed' ,
0 commit comments