Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions examples/wait-for-indexing-job.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
48 changes: 48 additions & 0 deletions src/resources/knowledge-bases/indexing-jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IndexingJobRetrieveResponse> {
const interval = opts?.interval ?? 2000;
const timeout = opts?.timeout ?? 1000 * 60 * 10; // 10 minutes

const start = Date.now();

const check = async (): Promise<IndexingJobRetrieveResponse> => {
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 {
Expand Down
54 changes: 54 additions & 0 deletions tests/api-resources/knowledge-bases/indexing-jobs.wait.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});