diff --git a/examples/wait-for-indexing-job.ts b/examples/wait-for-indexing-job.ts new file mode 100644 index 0000000..cda6264 --- /dev/null +++ b/examples/wait-for-indexing-job.ts @@ -0,0 +1,51 @@ +import Gradient from '@digitalocean/gradient'; + +// Example usage of the new waitForCompletion method +async function example() { + const client = new Gradient(); + + try { + // Start an indexing job + const createResponse = await client.knowledgeBases.indexingJobs.create({ + knowledge_base_uuid: 'your-kb-uuid', + }); + + const jobUuid = createResponse.job?.uuid; + if (!jobUuid) { + throw new Error('Failed to get job UUID'); + } + + console.log(`Started indexing job: ${jobUuid}`); + + // Wait for completion with custom interval and timeout + const result = await client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, { + interval: 5000, // Poll every 5 seconds + timeout: 1000 * 60 * 15, // 15 minute timeout + }); + + console.log('Indexing job completed successfully!', result.job); + } catch (error) { + if (error instanceof Error) { + if (error.message.includes('terminal failure phase')) { + console.error('Indexing job failed:', error.message); + } else if (error.message.includes('timeout')) { + console.error('Indexing job timed out'); + } else { + console.error('Unexpected error:', error.message); + } + } + } +} + +// Old way (no longer needed) +async function oldWay() { + const client = new Gradient(); + + while (true) { + const job = await client.knowledgeBases.indexingJobs.retrieve('123e4567-e89b-12d3-a456-426614174000'); + if (job.job?.phase !== 'BATCH_JOB_PHASE_RUNNING') { + break; + } + await new Promise((resolve) => setTimeout(resolve, 2000)); + } +} \ No newline at end of file diff --git a/src/resources/knowledge-bases/indexing-jobs.ts b/src/resources/knowledge-bases/indexing-jobs.ts index 0a54512..80103f7 100644 --- a/src/resources/knowledge-bases/indexing-jobs.ts +++ b/src/resources/knowledge-bases/indexing-jobs.ts @@ -113,6 +113,54 @@ export class IndexingJobs extends APIResource { ...options, }); } + + /** + * Poll the indexing job until it reaches a terminal state. + * + * By default this polls every 2000ms and times out after 10 minutes. + * It will resolve with the final retrieve response when the job succeeds, + * and reject if the job fails, is cancelled, or an error phase is reached. + * + * @example + * ```ts + * await client.knowledgeBases.indexingJobs.waitForCompletion('uuid'); + * ``` + */ + async waitForCompletion( + uuid: string, + opts: { interval?: number; timeout?: number } | undefined = {}, + ): Promise { + const interval = opts?.interval ?? 2000; + const timeout = opts?.timeout ?? 1000 * 60 * 10; // 10 minutes + + const start = Date.now(); + + const check = async (): Promise => { + const res = await this.retrieve(uuid); + const phase = res.job?.phase; + + if (phase === 'BATCH_JOB_PHASE_SUCCEEDED') { + return res; + } + + if ( + phase === 'BATCH_JOB_PHASE_FAILED' || + phase === 'BATCH_JOB_PHASE_ERROR' || + phase === 'BATCH_JOB_PHASE_CANCELLED' + ) { + throw new Error(`indexing job entered terminal failure phase: ${phase}`); + } + + if (Date.now() - start > timeout) { + throw new Error('timeout waiting for indexing job to complete'); + } + + await new Promise((r) => setTimeout(r, interval)); + return check(); + }; + + return check(); + } } export interface APIIndexedDataSource { diff --git a/tests/api-resources/knowledge-bases/indexing-jobs.wait.test.ts b/tests/api-resources/knowledge-bases/indexing-jobs.wait.test.ts new file mode 100644 index 0000000..492af8b --- /dev/null +++ b/tests/api-resources/knowledge-bases/indexing-jobs.wait.test.ts @@ -0,0 +1,54 @@ +import Gradient from '../../../src'; + +describe('resource indexingJobs waitForCompletion', () => { + test('resolves when phase becomes SUCCEEDED', async () => { + const client = new Gradient({ baseURL: 'http://localhost' }); + + // mock retrieve to simulate running then succeeded + let calls = 0; + (client.knowledgeBases.indexingJobs as any).retrieve = async (uuid: string) => { + calls += 1; + if (calls < 3) { + return { job: { uuid, phase: 'BATCH_JOB_PHASE_RUNNING' } }; + } + return { job: { uuid, phase: 'BATCH_JOB_PHASE_SUCCEEDED' } }; + }; + + const res = await client.knowledgeBases.indexingJobs.waitForCompletion('uuid', { + interval: 1, + timeout: 1000, + }); + expect(res.job?.phase).toBe('BATCH_JOB_PHASE_SUCCEEDED'); + expect(calls).toBeGreaterThanOrEqual(3); + }); + + test('rejects when phase becomes FAILED', async () => { + const client = new Gradient({ baseURL: 'http://localhost' }); + + (client.knowledgeBases.indexingJobs as any).retrieve = async (uuid: string) => { + return { job: { uuid, phase: 'BATCH_JOB_PHASE_FAILED' } }; + }; + + await expect( + client.knowledgeBases.indexingJobs.waitForCompletion('uuid', { + interval: 1, + timeout: 1000, + }), + ).rejects.toThrow(/terminal failure phase/); + }); + + test('rejects on timeout', async () => { + const client = new Gradient({ baseURL: 'http://localhost' }); + + (client.knowledgeBases.indexingJobs as any).retrieve = async (uuid: string) => { + return { job: { uuid, phase: 'BATCH_JOB_PHASE_RUNNING' } }; + }; + + await expect( + client.knowledgeBases.indexingJobs.waitForCompletion('uuid', { + interval: 1, + timeout: 10, + }), + ).rejects.toThrow(/timeout waiting for indexing job to complete/); + }); +});