Skip to content

Commit c73f0da

Browse files
authored
Added extended http1 options (#33)
You can now set, through the `DataAPIClient`, the following options: - `preferHttp2` - `maxTimeMS` (default maxTimeMS) - `http1.keepAlive` - `http1.keepAliveMs` - `http1.maxSockets` - `http1.maxFreeSockets` The `preferHttp2` field directly on the `DataAPIClientOptions` is deprecated, with a message to prefer to use the one in `DataAPIHttpOptions` instead
1 parent f6a37e9 commit c73f0da

File tree

11 files changed

+211
-54
lines changed

11 files changed

+211
-54
lines changed

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

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
RawDataAPIResponse,
2525
} from '@/src/api';
2626
import { DataAPIResponseError, DataAPITimeoutError, ObjectId, UUID, WithNamespace } from '@/src/data-api';
27-
import { MkTimeoutError, TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
27+
import { TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
2828
import { CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent } from '@/src/data-api/events';
2929
import { CollectionNotFoundError, DataAPIHttpError, mkRespErrorFromResponse } from '@/src/data-api/errors';
3030

@@ -56,10 +56,6 @@ export class DataAPIHttpClient extends HttpClient {
5656
super({
5757
...props,
5858
mkAuthHeader: (token) => ({ [DEFAULT_DATA_API_AUTH_HEADER]: token }),
59-
fetchCtx: {
60-
preferred: props.fetchCtx.preferred,
61-
closed: props.fetchCtx.closed,
62-
},
6359
});
6460
this.namespace = props.namespace;
6561
this.#props = props;
@@ -73,11 +69,11 @@ export class DataAPIHttpClient extends HttpClient {
7369
}
7470

7571
public timeoutManager(timeoutMs: number | undefined) {
76-
return mkTimeoutManager(timeoutMs);
72+
return this._mkTimeoutManager(timeoutMs);
7773
}
7874

7975
public async executeCommand(command: Record<string, any>, options: TimeoutOptions & ExecuteCommandOptions | undefined) {
80-
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
76+
const timeoutManager = options?.timeoutManager ?? this._mkTimeoutManager(options?.maxTimeMS);
8177

8278
return await this._requestDataAPI({
8379
url: this.baseUrl,
@@ -149,15 +145,11 @@ export class DataAPIHttpClient extends HttpClient {
149145
throw e;
150146
}
151147
}
152-
}
153-
154-
const mkTimeoutManager = (maxMs: number | undefined) => {
155-
const timeout = maxMs ?? DEFAULT_TIMEOUT;
156-
return new TimeoutManager(timeout, mkTimeoutErrorMaker(timeout));
157-
}
158148

159-
const mkTimeoutErrorMaker = (timeout: number): MkTimeoutError => {
160-
return () => new DataAPITimeoutError(timeout);
149+
private _mkTimeoutManager(timeout: number | undefined) {
150+
timeout ??= this.fetchCtx.maxTimeMS ?? DEFAULT_TIMEOUT;
151+
return new TimeoutManager(timeout, () => new DataAPITimeoutError(timeout));
152+
}
161153
}
162154

163155
const mkFauxErroredResponse = (message: string): RawDataAPIResponse => {

src/api/devops-api-http-client.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { hrTimeMs, HttpClient } from '@/src/api/http-client';
1717
import { APIResponse, HTTPClientOptions, HttpMethodStrings } from '@/src/api/types';
1818
import { DevOpsAPIResponseError, DevOpsAPITimeoutError, DevOpsUnexpectedStateError } from '@/src/devops/errors';
1919
import { AdminBlockingOptions } from '@/src/devops/types';
20-
import { MkTimeoutError, TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
20+
import { TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
2121
import { DEFAULT_DEVOPS_API_AUTH_HEADER, HttpMethods } from '@/src/api/constants';
2222
import {
2323
AdminCommandFailedEvent,
@@ -56,17 +56,17 @@ export class DevOpsAPIHttpClient extends HttpClient {
5656
...props,
5757
mkAuthHeader: (token) => ({ [DEFAULT_DEVOPS_API_AUTH_HEADER]: `Bearer ${token}` }),
5858
fetchCtx: {
59+
...props.fetchCtx,
5960
preferred: props.fetchCtx.http1,
60-
closed: props.fetchCtx.closed,
61-
}
61+
},
6262
});
6363
}
6464

6565
public async request(req: DevOpsAPIRequestInfo, options: TimeoutOptions | undefined, started: number = 0): Promise<APIResponse> {
6666
const isLongRunning = started !== 0;
6767

6868
try {
69-
const timeoutManager = options?.timeoutManager ?? mkTimeoutManager(options?.maxTimeMS);
69+
const timeoutManager = options?.timeoutManager ?? this._mkTimeoutManager(options?.maxTimeMS);
7070
const url = this.baseUrl + req.path;
7171

7272
if (this.monitorCommands && !isLongRunning) {
@@ -112,7 +112,7 @@ export class DevOpsAPIHttpClient extends HttpClient {
112112
}
113113

114114
public async requestLongRunning(req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo): Promise<APIResponse> {
115-
const timeoutManager = mkTimeoutManager(info.options?.maxTimeMS);
115+
const timeoutManager = this._mkTimeoutManager(info.options?.maxTimeMS);
116116
const isLongRunning = info?.options?.blocking !== false;
117117

118118
if (this.monitorCommands) {
@@ -183,13 +183,9 @@ export class DevOpsAPIHttpClient extends HttpClient {
183183
});
184184
}
185185
}
186-
}
187186

188-
const mkTimeoutManager = (maxMs: number | undefined) => {
189-
const timeout = maxMs ?? 0;
190-
return new TimeoutManager(timeout, mkTimeoutErrorMaker(timeout));
191-
}
192-
193-
const mkTimeoutErrorMaker = (timeout: number): MkTimeoutError => {
194-
return (info) => new DevOpsAPITimeoutError(info.url, timeout);
187+
private _mkTimeoutManager(timeout: number | undefined) {
188+
timeout ??= this.fetchCtx.maxTimeMS ?? (12 * 60 * 1000);
189+
return new TimeoutManager(timeout, (info) => new DevOpsAPITimeoutError(info.url, timeout));
190+
}
195191
}

src/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface FetchCtx {
4747
http1: ReturnType<typeof context>,
4848
preferredType: 'http1' | 'http2',
4949
closed: { ref: boolean },
50+
maxTimeMS: number | undefined,
5051
}
5152

5253
/**
@@ -55,6 +56,7 @@ export interface FetchCtx {
5556
export interface InternalFetchCtx {
5657
preferred: ReturnType<typeof context>,
5758
closed: { ref: boolean },
59+
maxTimeMS: number | undefined,
5860
}
5961

6062
/**

src/client/data-api-client.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
// noinspection JSDeprecatedSymbols
1415

1516
import { Db, mkDb, validateDbOpts } from '@/src/data-api/db';
1617
import { AstraAdmin, mkAdmin, validateAdminOpts } from '@/src/devops/astra-admin';
1718
import {
1819
AdminSpawnOptions,
1920
Caller,
21+
DataAPIClientOptions,
22+
DataAPIHttpOptions,
2023
DbSpawnOptions,
2124
InternalRootClientOpts,
22-
DataAPIClientOptions,
2325
} from '@/src/client/types';
2426
import TypedEmitter from 'typed-emitter';
2527
import EventEmitter from 'events';
2628
import { DataAPICommandEvents } from '@/src/data-api/events';
2729
import { AdminCommandEvents } from '@/src/devops';
2830
import { validateOption } from '@/src/data-api/utils';
29-
import { context } from 'fetch-h2';
31+
import { context, ContextOptions } from 'fetch-h2';
3032
import { buildUserAgent } from '@/src/api';
3133

3234
/**
@@ -103,17 +105,25 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase {
103105

104106
validateRootOpts(options);
105107

106-
const baseCtxOptions = {
108+
const baseCtxOptions: Partial<ContextOptions> = {
107109
userAgent: buildUserAgent(options?.caller),
108110
overwriteUserAgent: true,
111+
http1: {
112+
keepAlive: options?.httpOptions?.http1?.keepAlive,
113+
keepAliveMsecs: options?.httpOptions?.http1?.keepAliveMS,
114+
maxSockets: options?.httpOptions?.http1?.maxSockets,
115+
maxFreeSockets: options?.httpOptions?.http1?.maxFreeSockets,
116+
},
109117
};
110118

111119
const http1Ctx = context({
112120
...baseCtxOptions,
113121
httpsProtocols: ['http1'],
114122
});
115123

116-
const preferredCtx = (options?.preferHttp2 !== false)
124+
const preferHttp2 = options?.httpOptions?.preferHttp2 ?? getDeprecatedPrefersHttp2(options) ?? true;
125+
126+
const preferredCtx = (preferHttp2)
117127
? context(baseCtxOptions)
118128
: http1Ctx;
119129

@@ -122,10 +132,11 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase {
122132
fetchCtx: {
123133
http1: http1Ctx,
124134
preferred: preferredCtx,
125-
preferredType: (options?.preferHttp2 !== false)
135+
preferredType: (preferHttp2)
126136
? 'http2'
127137
: 'http1',
128138
closed: { ref: false },
139+
maxTimeMS: options?.httpOptions?.maxTimeMS,
129140
},
130141
dbOptions: {
131142
monitorCommands: false,
@@ -299,6 +310,11 @@ export class DataAPIClient extends DataAPIClientEventEmitterBase {
299310
public [Symbol.asyncDispose]!: () => Promise<void>;
300311
}
301312

313+
// Shuts the linter up about 'preferHttp2' not being deprecated
314+
function getDeprecatedPrefersHttp2(opts: DataAPIClientOptions | undefined | null) {
315+
return opts?.[('preferHttp2' as any as null)!];
316+
}
317+
302318
function validateRootOpts(opts: DataAPIClientOptions | undefined | null) {
303319
validateOption('root client options', opts, 'object');
304320

@@ -307,12 +323,29 @@ function validateRootOpts(opts: DataAPIClientOptions | undefined | null) {
307323
}
308324

309325
validateOption('caller', opts.caller, 'object', validateCaller);
310-
311-
validateOption('preferHttp2 option', opts.preferHttp2, 'boolean');
326+
validateOption('preferHttp2 option', getDeprecatedPrefersHttp2(opts), 'boolean');
312327

313328
validateDbOpts(opts.dbOptions);
314-
315329
validateAdminOpts(opts.adminOptions);
330+
validateHttpOpts(opts.httpOptions);
331+
}
332+
333+
function validateHttpOpts(opts: DataAPIHttpOptions | undefined | null) {
334+
validateOption('http options', opts, 'object');
335+
336+
if (!opts) {
337+
return;
338+
}
339+
340+
validateOption('preferHttp2 option', opts.preferHttp2, 'boolean');
341+
validateOption('maxTimeMS option', opts.maxTimeMS, 'number');
342+
343+
validateOption('http1 options', opts.http1, 'object', (http1) => {
344+
validateOption('http1.keepAlive option', http1.keepAlive, 'boolean');
345+
validateOption('http1.keepAliveMS option', http1.keepAliveMS, 'number');
346+
validateOption('http1.maxSockets option', http1.maxSockets, 'number');
347+
validateOption('http1.maxFreeSockets option', http1.maxFreeSockets, 'number');
348+
});
316349
}
317350

318351
function validateCaller(caller: Caller | Caller[]) {

src/client/types.ts

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,9 @@ export type Caller = [name: string, version?: string];
4444
*/
4545
export interface DataAPIClientOptions {
4646
/**
47-
* Whether to prefer HTTP/2 for requests to the Data API.
48-
*
49-
* Both versions are generally interchangeable, but HTTP2 is generally recommended for better performance.
50-
*
51-
* Defaults to `true` if never provided.
52-
*
53-
* @defaultValue true
47+
* The client-wide options related to http operations.
5448
*/
55-
preferHttp2?: boolean,
49+
httpOptions?: DataAPIHttpOptions,
5650
/**
5751
* The default options when spawning a {@link Db} instance.
5852
*/
@@ -90,6 +84,83 @@ export interface DataAPIClientOptions {
9084
* ```
9185
*/
9286
caller?: Caller | Caller[],
87+
/**
88+
* **Prefer to use the {@link httpOptions} property instead.**
89+
*
90+
* @deprecated
91+
*
92+
* @see DataAPIHttpOptions
93+
*/
94+
preferHttp2?: boolean,
95+
}
96+
97+
/**
98+
* The options available for the {@link DataAPIClient} related to making HTTP requests.
99+
*/
100+
export interface DataAPIHttpOptions {
101+
/**
102+
* Whether to prefer HTTP/2 for requests to the Data API; if set to `false`, HTTP/1.1 will be used instead.
103+
*
104+
* **Note that this is only available when using the Data API; the DevOps API does not support HTTP/2**
105+
*
106+
* Both versions are generally interchangeable, but HTTP2 is generally recommended for better performance.
107+
*
108+
* Defaults to `true` if never provided.
109+
*
110+
* @defaultValue true
111+
*/
112+
preferHttp2?: boolean,
113+
/**
114+
* The default maximum time in milliseconds to wait for a response from the server.
115+
*
116+
* This does *not* mean the request will be cancelled after this time, but rather that the client will wait
117+
* for this time before considering the request to have timed out.
118+
*
119+
* The request may or may not still be running on the server after this time.
120+
*/
121+
maxTimeMS?: number,
122+
/**
123+
* Options specific to HTTP/1.1 requests.
124+
*/
125+
http1?: DataAPIHttp1Options,
126+
}
127+
128+
/**
129+
* The options available for the {@link DataAPIClient} related to making HTTP/1.1 requests.
130+
*/
131+
export interface DataAPIHttp1Options {
132+
/**
133+
* Whether to keep the connection alive for future requests. This is generally recommended for better performance.
134+
*
135+
* Defaults to true.
136+
*
137+
* @defaultValue true
138+
*/
139+
keepAlive?: boolean,
140+
/**
141+
* The delay (in milliseconds) before keep-alive probing.
142+
*
143+
* Defaults to 1000ms.
144+
*
145+
* @defaultValue 1000
146+
*/
147+
keepAliveMS?: number,
148+
/**
149+
* Maximum number of sockets to allow per origin.
150+
*
151+
* Defaults to 256.
152+
*
153+
* @defaultValue 256
154+
*/
155+
maxSockets?: number,
156+
/**
157+
* Maximum number of lingering sockets, waiting to be re-used for new requests.
158+
*
159+
* Defaults to Infinity.
160+
*
161+
* @defaultValue Infinity
162+
*/
163+
maxFreeSockets?: number,
93164
}
94165

95166
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface ListCollectionsCommand {
3030
*
3131
* @field nameOnly - If true, only the name of the collection is returned. If false, the full collection info is returned. Defaults to true.
3232
* @field namespace - Overrides the namespace to list collections from. If not provided, the default namespace is used.
33-
* @field maxTimeMs - The maximum amount of time to allow the operation to run.
33+
* @field maxTimeMS - The maximum amount of time to allow the operation to run.
3434
*
3535
* @see Db.listCollections
3636
*

src/data-api/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function replaceAstraUrlIdAndRegion(uri: string, id: string, region: stri
7070
/**
7171
* @internal
7272
*/
73-
export function validateOption<T = unknown>(name: string, obj: unknown, type: string, test?: (obj: T) => void): void {
73+
export function validateOption<T>(name: string, obj: T, type: string, test?: (obj: NonNullable<T>) => void): void {
7474
if (obj === null || obj === undefined) {
7575
return;
7676
}
@@ -79,7 +79,7 @@ export function validateOption<T = unknown>(name: string, obj: unknown, type: st
7979
throw new TypeError(`Invalid ${name}; expected a ${type} value, or undefined/null`);
8080
}
8181

82-
test?.(obj as T);
82+
test?.(obj);
8383
}
8484

8585
/**

tests/fixtures.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const initTestObjects = async (ctx: Context, preferHttp2 = USE_HTTP2): Pr
3434
ctx.skip();
3535
}
3636

37-
const client = new DataAPIClient(process.env.APPLICATION_TOKEN!, { preferHttp2 });
37+
const client = new DataAPIClient(process.env.APPLICATION_TOKEN!, { httpOptions: { preferHttp2 } });
3838
const db = client.db(process.env.ASTRA_URI!);
3939

4040
const coll = (!collCreated)

0 commit comments

Comments
 (0)