Skip to content

Commit 12d1af3

Browse files
yashranawaybbatha
andauthored
feat(knowledge-bases): add waitForDatabase polling helper (#8)
Add helper function to poll knowledge base database status until ONLINE state. Includes error handling for failed states, timeout protection, and configurable polling intervals. Fixes the need for manual polling loops. Co-authored-by: Ben Batha <[email protected]>
1 parent a355858 commit 12d1af3

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ export {
2020
PermissionDeniedError,
2121
UnprocessableEntityError,
2222
} from './core/error';
23+
24+
// Export knowledge base helpers
25+
export {
26+
waitForDatabase,
27+
type WaitForDatabaseOptions,
28+
WaitForDatabaseTimeoutError,
29+
WaitForDatabaseFailedError,
30+
} from './resources/knowledge-bases/wait-for-database';
2331
export {
2432
IndexingJobAbortedError,
2533
IndexingJobNotFoundError,

src/resources/knowledge-bases/knowledge-bases.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
import { APIPromise } from '../../core/api-promise';
3737
import { RequestOptions } from '../../internal/request-options';
3838
import { path } from '../../internal/utils/path';
39+
import { waitForDatabase } from './wait-for-database';
3940

4041
export class KnowledgeBases extends APIResource {
4142
dataSources: DataSourcesAPI.DataSources = new DataSourcesAPI.DataSources(this._client);
@@ -137,6 +138,31 @@ export class KnowledgeBases extends APIResource {
137138
...options,
138139
});
139140
}
141+
142+
/**
143+
* Polls for knowledge base database creation to complete.
144+
*
145+
* This helper method polls the knowledge base status until the database is ONLINE,
146+
* handling various error states and providing configurable timeout and polling intervals.
147+
*
148+
* @example
149+
* ```ts
150+
* const kb = await client.knowledgeBases.waitForDatabase('123e4567-e89b-12d3-a456-426614174000');
151+
* console.log('Database is ready:', kb.database_status); // 'ONLINE'
152+
* ```
153+
*
154+
* @param uuid - The knowledge base UUID to poll for
155+
* @param options - Configuration options for polling behavior
156+
* @returns Promise<KnowledgeBaseRetrieveResponse> - The knowledge base with ONLINE database status
157+
* @throws WaitForDatabaseTimeoutError - If polling times out
158+
* @throws WaitForDatabaseFailedError - If the database enters a failed state
159+
*/
160+
async waitForDatabase(
161+
uuid: string,
162+
options?: import('./wait-for-database').WaitForDatabaseOptions,
163+
): Promise<KnowledgeBaseRetrieveResponse> {
164+
return waitForDatabase(this._client, uuid, options || {});
165+
}
140166
}
141167

142168
/**
@@ -424,6 +450,13 @@ export interface KnowledgeBaseListParams {
424450
KnowledgeBases.DataSources = DataSources;
425451
KnowledgeBases.IndexingJobs = IndexingJobs;
426452

453+
export {
454+
waitForDatabase,
455+
WaitForDatabaseOptions,
456+
WaitForDatabaseTimeoutError,
457+
WaitForDatabaseFailedError,
458+
} from './wait-for-database';
459+
427460
export declare namespace KnowledgeBases {
428461
export {
429462
type APIKnowledgeBase as APIKnowledgeBase,
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { Gradient } from '../../client';
4+
import { GradientError } from '../../core/error';
5+
import { sleep } from '../../internal/utils/sleep';
6+
import { KnowledgeBaseRetrieveResponse } from './knowledge-bases';
7+
import { RequestOptions } from '../../internal/request-options';
8+
9+
export interface WaitForDatabaseOptions {
10+
/**
11+
* The polling interval in milliseconds. Defaults to 5000 (5 seconds).
12+
*/
13+
interval?: number;
14+
15+
/**
16+
* The maximum time to wait in milliseconds. Defaults to 600000 (10 minutes).
17+
*/
18+
timeout?: number;
19+
20+
/**
21+
* AbortSignal to cancel the polling operation.
22+
*/
23+
signal?: AbortSignal;
24+
25+
/**
26+
* Additional request options to pass through to the knowledge base retrieval request.
27+
*/
28+
requestOptions?: RequestOptions;
29+
}
30+
31+
/**
32+
* Database status values that indicate a successful deployment.
33+
*/
34+
const ONLINE_STATUSES = ['ONLINE'] as const;
35+
36+
/**
37+
* Database status values that indicate a failed deployment.
38+
*/
39+
const FAILED_STATUSES = ['DECOMMISSIONED', 'UNHEALTHY'] as const;
40+
41+
/**
42+
* Database status values that indicate the deployment is still in progress.
43+
*/
44+
const PENDING_STATUSES = [
45+
'CREATING',
46+
'POWEROFF',
47+
'REBUILDING',
48+
'REBALANCING',
49+
'FORKING',
50+
'MIGRATING',
51+
'RESIZING',
52+
'RESTORING',
53+
'POWERING_ON',
54+
] as const;
55+
56+
export class WaitForDatabaseTimeoutError extends GradientError {
57+
constructor(message: string, kbId?: string, timeout?: number) {
58+
super(message);
59+
this.name = 'WaitForDatabaseTimeoutError';
60+
if (kbId) {
61+
(this as any).knowledgeBaseId = kbId;
62+
(this as any).timeout = timeout;
63+
}
64+
}
65+
}
66+
67+
export class WaitForDatabaseFailedError extends GradientError {
68+
constructor(message: string, kbId?: string, status?: string) {
69+
super(message);
70+
this.name = 'WaitForDatabaseFailedError';
71+
if (kbId) {
72+
(this as any).knowledgeBaseId = kbId;
73+
(this as any).databaseStatus = status;
74+
}
75+
}
76+
}
77+
78+
/**
79+
* Polls for knowledge base database creation to complete.
80+
*
81+
* This helper function polls the knowledge base status until the database is ONLINE,
82+
* handling various error states and providing configurable timeout and polling intervals.
83+
*
84+
* @example
85+
* ```ts
86+
* import Gradient from '@digitalocean/gradient';
87+
*
88+
* const client = new Gradient();
89+
*
90+
* // Basic usage
91+
* try {
92+
* const kb = await client.knowledgeBases.waitForDatabase('123e4567-e89b-12d3-a456-426614174000');
93+
* console.log('Database is ready:', kb.database_status); // 'ONLINE'
94+
* } catch (error) {
95+
* if (error instanceof WaitForDatabaseTimeoutError) {
96+
* console.log('Polling timed out');
97+
* } else if (error instanceof WaitForDatabaseFailedError) {
98+
* console.log('Database deployment failed');
99+
* }
100+
* }
101+
*
102+
* // With AbortSignal
103+
* const controller = new AbortController();
104+
* setTimeout(() => controller.abort(), 30000); // Cancel after 30 seconds
105+
*
106+
* try {
107+
* const kb = await client.knowledgeBases.waitForDatabase('123e4567-e89b-12d3-a456-426614174000', {
108+
* signal: controller.signal
109+
* });
110+
* } catch (error) {
111+
* if (error.message === 'Operation was aborted') {
112+
* console.log('Operation was cancelled');
113+
* }
114+
* }
115+
* ```
116+
*
117+
* @param client - The Gradient client instance
118+
* @param uuid - The knowledge base UUID to poll for
119+
* @param options - Configuration options for polling behavior
120+
* @returns Promise<KnowledgeBaseRetrieveResponse> - The knowledge base with ONLINE database status
121+
* @throws WaitForDatabaseTimeoutError - If polling times out
122+
* @throws WaitForDatabaseFailedError - If the database enters a failed state
123+
* @throws Error - If the operation is aborted via AbortSignal
124+
*/
125+
export async function waitForDatabase(
126+
client: Gradient,
127+
uuid: string,
128+
options: WaitForDatabaseOptions,
129+
): Promise<KnowledgeBaseRetrieveResponse> {
130+
const { interval = 5000, timeout = 600000, signal, requestOptions } = options;
131+
132+
const startTime = Date.now();
133+
134+
while (true) {
135+
// Check if operation was aborted
136+
if (signal?.aborted) {
137+
throw new Error('Operation was aborted');
138+
}
139+
140+
const elapsed = Date.now() - startTime;
141+
142+
if (elapsed > timeout) {
143+
throw new WaitForDatabaseTimeoutError(
144+
`Knowledge base database ${uuid} did not become ONLINE within ${timeout}ms`,
145+
uuid,
146+
timeout,
147+
);
148+
}
149+
150+
try {
151+
const response = await client.knowledgeBases.retrieve(uuid, requestOptions);
152+
const status = response.database_status;
153+
154+
if (!status) {
155+
// If database_status is not present, continue polling
156+
await sleep(interval);
157+
continue;
158+
}
159+
160+
// Check for successful completion
161+
if (ONLINE_STATUSES.includes(status as any)) {
162+
return response;
163+
}
164+
165+
// Check for failed states
166+
if (FAILED_STATUSES.includes(status as any)) {
167+
throw new WaitForDatabaseFailedError(
168+
`Knowledge base database ${uuid} entered failed state: ${status}`,
169+
uuid,
170+
status,
171+
);
172+
}
173+
174+
// Check if still in progress
175+
if (PENDING_STATUSES.includes(status as any)) {
176+
await sleep(interval);
177+
continue;
178+
}
179+
180+
// Unknown status - treat as error for safety
181+
throw new WaitForDatabaseFailedError(
182+
`Knowledge base database ${uuid} entered unknown state: ${status}`,
183+
uuid,
184+
status,
185+
);
186+
} catch (error) {
187+
// If it's our custom error, re-throw it
188+
if (error instanceof WaitForDatabaseFailedError || error instanceof WaitForDatabaseTimeoutError) {
189+
throw error;
190+
}
191+
192+
// For other errors (network issues, etc.), try waiting a bit longer before retrying
193+
await sleep(Math.min(interval * 2, 30000)); // Max 30 seconds between retries
194+
continue;
195+
}
196+
}
197+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import Gradient from '@digitalocean/gradient';
4+
import {
5+
WaitForDatabaseFailedError,
6+
WaitForDatabaseTimeoutError,
7+
waitForDatabase,
8+
} from '../../../src/resources/knowledge-bases/wait-for-database';
9+
10+
const client = new Gradient({
11+
accessToken: 'My Access Token',
12+
modelAccessKey: 'My Model Access Key',
13+
agentAccessKey: 'My Agent Access Key',
14+
baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
15+
});
16+
17+
describe('waitForDatabase', () => {
18+
const kbUuid = '123e4567-e89b-12d3-a456-426614174000';
19+
20+
beforeEach(() => {
21+
jest.resetAllMocks();
22+
});
23+
24+
describe('error classes', () => {
25+
it('should create WaitForDatabaseTimeoutError with correct properties', () => {
26+
const error = new WaitForDatabaseTimeoutError('Test timeout', kbUuid, 1000);
27+
28+
expect(error.name).toBe('WaitForDatabaseTimeoutError');
29+
expect(error.message).toBe('Test timeout');
30+
expect(error).toBeInstanceOf(Error);
31+
});
32+
33+
it('should create WaitForDatabaseFailedError with correct properties', () => {
34+
const error = new WaitForDatabaseFailedError('Test failure', kbUuid, 'DECOMMISSIONED');
35+
36+
expect(error.name).toBe('WaitForDatabaseFailedError');
37+
expect(error.message).toBe('Test failure');
38+
expect(error).toBeInstanceOf(Error);
39+
});
40+
});
41+
42+
describe('function parameters', () => {
43+
it('should accept correct parameters', () => {
44+
expect(typeof waitForDatabase).toBe('function');
45+
expect(waitForDatabase.length).toBe(3);
46+
});
47+
48+
it('should use default options when none provided', () => {
49+
const options = {};
50+
expect(waitForDatabase).toBeDefined();
51+
// Function should exist and be callable (will fail at runtime due to mocking, but should compile)
52+
});
53+
});
54+
55+
describe('status constants', () => {
56+
it('should handle different database status values', () => {
57+
// Test that status strings are handled correctly
58+
const statuses = [
59+
'ONLINE',
60+
'CREATING',
61+
'REBUILDING',
62+
'RESIZING',
63+
'POWERING_ON',
64+
'DECOMMISSIONED',
65+
'UNHEALTHY',
66+
];
67+
68+
statuses.forEach((status) => {
69+
expect(typeof status).toBe('string');
70+
});
71+
});
72+
});
73+
74+
describe('integration test placeholder', () => {
75+
it('should integrate with knowledge base retrieval', async () => {
76+
// This is a placeholder test - actual integration tests would require a running mock server
77+
expect(client.knowledgeBases).toBeDefined();
78+
expect(client.knowledgeBases.retrieve).toBeDefined();
79+
});
80+
});
81+
});

0 commit comments

Comments
 (0)