Skip to content

Commit 7d26f2d

Browse files
authored
Kg collection default timeout (#41)
* collection-level timeout * restructured timeout tests
1 parent a9ada5d commit 7d26f2d

File tree

7 files changed

+128
-96
lines changed

7 files changed

+128
-96
lines changed

src/api/clients/data-api-http-client.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { DataAPIResponseError, DataAPITimeoutError, ObjectId, UUID } from '@/src
2828
import { TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
2929
import { CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent } from '@/src/data-api/events';
3030
import { CollectionNotFoundError, DataAPIHttpError, mkRespErrorFromResponse } from '@/src/data-api/errors';
31+
import { CollectionSpawnOptions } from '@/src/data-api/types/collections/spawn-collection';
3132

3233
/**
3334
* @internal
@@ -55,18 +56,21 @@ interface DataAPIHttpClientOptions extends HTTPClientOptions {
5556
export class DataAPIHttpClient extends HttpClient {
5657
public collection?: string;
5758
public namespace?: string;
59+
public maxTimeMS: number;
5860
readonly #props: DataAPIHttpClientOptions;
5961

6062
constructor(props: DataAPIHttpClientOptions, embeddingApiKey?: string) {
6163
super(props, mkHeaders(embeddingApiKey));
6264
this.namespace = props.namespace;
6365
this.#props = props;
66+
this.maxTimeMS = this.fetchCtx.maxTimeMS ?? DEFAULT_TIMEOUT;
6467
}
6568

66-
public forCollection(namespace: string, collection: string, embeddingApiKey: string | undefined): DataAPIHttpClient {
67-
const clone = new DataAPIHttpClient(this.#props, embeddingApiKey);
69+
public forCollection(namespace: string, collection: string, opts: CollectionSpawnOptions | undefined): DataAPIHttpClient {
70+
const clone = new DataAPIHttpClient(this.#props, opts?.embeddingApiKey);
6871
clone.collection = collection;
6972
clone.namespace = namespace;
73+
clone.maxTimeMS = opts?.defaultMaxTimeMS ?? this.maxTimeMS;
7074
return clone;
7175
}
7276

@@ -133,8 +137,8 @@ export class DataAPIHttpClient extends HttpClient {
133137
}
134138

135139
const respData = {
136-
status: data?.status,
137140
data: data?.data,
141+
status: data?.status,
138142
errors: data?.errors,
139143
}
140144

@@ -152,7 +156,7 @@ export class DataAPIHttpClient extends HttpClient {
152156
}
153157

154158
private _mkTimeoutManager(timeout: number | undefined) {
155-
timeout ??= this.fetchCtx.maxTimeMS ?? DEFAULT_TIMEOUT;
159+
timeout ??= this.maxTimeMS;
156160
return new TimeoutManager(timeout, () => new DataAPITimeoutError(timeout));
157161
}
158162
}

src/data-api/collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class Collection<Schema extends SomeDoc = SomeDoc> {
131131
});
132132

133133
Object.defineProperty(this, '_httpClient', {
134-
value: httpClient.forCollection(this.namespace, this.collectionName, opts?.embeddingApiKey),
134+
value: httpClient.forCollection(this.namespace, this.collectionName, opts),
135135
enumerable: false,
136136
});
137137

src/data-api/types/collections/spawn-collection.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@
1414

1515
import { WithNamespace } from '@/src/data-api';
1616

17+
/**
18+
* Options for spawning a new collection.
19+
*/
1720
export interface CollectionSpawnOptions extends WithNamespace {
18-
embeddingApiKey?: string;
21+
/**
22+
* The API key for the embedding service to use
23+
*/
24+
embeddingApiKey?: string,
25+
/**
26+
* The default `maxTimeMS` for all operations on the collection. Will override the maxTimeMS set in the DataAPIClient
27+
* options; it can be overridden on a per-operation basis.
28+
*
29+
* This does *not* mean the request will be cancelled after this time, but rather that the client will wait
30+
* for this time before considering the request to have timed out.
31+
*
32+
* The request may or may not still be running on the server after this time.
33+
*/
34+
defaultMaxTimeMS?: number,
1935
}

tests/integration/api/data-api-http-client.test.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Collection, DataAPIResponseError, DataAPITimeoutError, Db } from '@/src
1717
import { DEFAULT_COLLECTION_NAME, initTestObjects, OTHER_NAMESPACE } from '@/tests/fixtures';
1818
import { DataAPIHttpClient } from '@/src/api';
1919
import assert from 'assert';
20+
import { DataAPIHttpError } from '@/src/data-api/errors';
2021

2122
describe('integration.api.data-api-http-client', () => {
2223
let httpClient: DataAPIHttpClient;
@@ -72,32 +73,5 @@ describe('integration.api.data-api-http-client', () => {
7273
assert.strictEqual(e.errorDescriptors[0].message, 'Authentication failed; is your token valid?');
7374
}
7475
});
75-
76-
it('should timeout properly', async () => {
77-
await assert.rejects(async () => {
78-
await httpClient.executeCommand({
79-
findCollections: {},
80-
}, {
81-
namespace: OTHER_NAMESPACE,
82-
maxTimeMS: 1,
83-
});
84-
}, DataAPITimeoutError);
85-
});
86-
87-
// it('should throw DataAPIHttpError on non-2XX responses', async function () {
88-
// try {
89-
// const [client] = await initTestObjects(this, false);
90-
// const httpClient = client.db('https://f1183f15-dc85-4fbf-8aae-f1ca97338bbb-us-east-1.apps.astra.datastax.com', { token: 'invalid-token' })['_httpClient'];
91-
// await httpClient.executeCommand({
92-
// findCollections: {},
93-
// }, {});
94-
// } catch (e) {
95-
// console.log(e);
96-
// assert.ok(e instanceof DataAPIHttpError);
97-
// assert.strictEqual(e.status, 502);
98-
// assert.ok(typeof e.body === 'string');
99-
// assert.strictEqual(e.raw.httpVersion, 1);
100-
// }
101-
// });
10276
});
10377
});

tests/integration/api/devops-http-client.test.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

tests/integration/client/data-api-client.test.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,32 +40,6 @@ describe('integration.client.data-api-client', () => {
4040
});
4141
});
4242

43-
describe('timeout', () => {
44-
it('times out on base timeout', async () => {
45-
const client = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 1 } });
46-
const db = client.db(process.env.ASTRA_URI!);
47-
const collection = db.collection(DEFAULT_COLLECTION_NAME);
48-
49-
try {
50-
await collection.insertOne({ name: 'Nightwish' });
51-
assert.fail('should have thrown an error');
52-
} catch (e) {
53-
assert.ok(e instanceof DataAPITimeoutError);
54-
assert.strictEqual(e.timeout, 1);
55-
}
56-
});
57-
58-
it('overrides base timeout', async () => {
59-
const client = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 1 } });
60-
const db = client.db(process.env.ASTRA_URI!);
61-
const collection = db.collection(DEFAULT_COLLECTION_NAME);
62-
63-
await assert.doesNotReject(async () => {
64-
await collection.insertOne({ name: 'Nightwish' }, { maxTimeMS: 10000 });
65-
});
66-
});
67-
});
68-
6943
describe('close', () => {
7044
it('should not allow operations after closing the client', async () => {
7145
const client = new DataAPIClient(process.env.APPLICATION_TOKEN!);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright DataStax, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
// noinspection DuplicatedCode
15+
16+
import { DEFAULT_COLLECTION_NAME, initTestObjects } from '@/tests/fixtures';
17+
import { Collection, DataAPITimeoutError, Db } from '@/src/data-api';
18+
import assert from 'assert';
19+
import { HttpMethods } from '@/src/api';
20+
import { DevOpsAPITimeoutError } from '@/src/devops';
21+
import { DataAPIClient } from '@/src/client';
22+
import process from 'process';
23+
24+
describe('integration.misc.timeouts', () => {
25+
let db: Db;
26+
let collection: Collection;
27+
28+
before(async function () {
29+
[, db, collection] = await initTestObjects(this);
30+
});
31+
32+
describe('in data-api', () => {
33+
it('should timeout @ the http-client level', async () => {
34+
const httpClient = collection['_httpClient'];
35+
36+
await assert.rejects(async () => {
37+
await httpClient.executeCommand({ findCollections: {} }, { maxTimeMS: 5 });
38+
}, DataAPITimeoutError);
39+
});
40+
41+
it('should timeout based on DataAPIClient maxTimeMS', async () => {
42+
const collection = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 1 } })
43+
.db(process.env.ASTRA_URI!)
44+
.collection(DEFAULT_COLLECTION_NAME);
45+
46+
await assert.rejects(async () => {
47+
await collection.insertOne({ name: 'Nightwish' });
48+
}, DataAPITimeoutError);
49+
});
50+
51+
it('should timeout based on collection maxTimeMS', async () => {
52+
const collection = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 30000 } })
53+
.db(process.env.ASTRA_URI!)
54+
.collection(DEFAULT_COLLECTION_NAME, { defaultMaxTimeMS: 1 });
55+
56+
await assert.rejects(async () => {
57+
await collection.insertOne({ name: 'Nightwish' });
58+
}, DataAPITimeoutError);
59+
});
60+
61+
it('should timeout based on operation maxTimeMS', async () => {
62+
const collection = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 30000 } })
63+
.db(process.env.ASTRA_URI!)
64+
.collection(DEFAULT_COLLECTION_NAME, { defaultMaxTimeMS: 30000 });
65+
66+
await assert.rejects(async () => {
67+
await collection.insertOne({ name: 'Nightwish' }, { maxTimeMS: 1 });
68+
}, DataAPITimeoutError);
69+
});
70+
});
71+
72+
describe('in devops', () => {
73+
it('should timeout @ the http-client level', async () => {
74+
const httpClient = db.admin()['_httpClient'];
75+
76+
await assert.rejects(async () => {
77+
await httpClient.request({ method: HttpMethods.Get, path: '/databases' }, { maxTimeMS: 5 });
78+
}, DevOpsAPITimeoutError);
79+
});
80+
81+
it('should timeout based on DataAPIClient maxTimeMS', async () => {
82+
const admin = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 1 } })
83+
.db(process.env.ASTRA_URI!)
84+
.admin();
85+
86+
await assert.rejects(async () => {
87+
await admin.listNamespaces();
88+
}, DevOpsAPITimeoutError);
89+
});
90+
91+
it('should timeout based on operation maxTimeMS', async () => {
92+
const admin = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { maxTimeMS: 30000 } })
93+
.db(process.env.ASTRA_URI!)
94+
.admin();
95+
96+
await assert.rejects(async () => {
97+
await admin.listNamespaces({ maxTimeMS: 1 });
98+
}, DevOpsAPITimeoutError);
99+
});
100+
});
101+
});

0 commit comments

Comments
 (0)