Skip to content

Commit 09675fd

Browse files
authored
Non-standard runtime support (#37)
Adds in support for runtimes that don't properly support the default `fetch-h2` http client by adding a native fetch implementation which is set as the default for non-node environments It also evolved into a bit of a refactoring of the `api/` stuff
1 parent c4a0eaf commit 09675fd

32 files changed

+465
-300
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ APPLICATION_TOKEN=AstraCS:<rest_of_token>
88
ASTRA_RUN_VECTORIZE_TESTS=
99

1010
# Set this to some value to enable running long-running tests
11-
ASTRA_RUN_LONG_TESTS`=
11+
ASTRA_RUN_LONG_TESTS=
1212

1313
# Set this to some value to enable running admin tests
1414
ASTRA_RUN_ADMIN_TESTS=
1515

1616
# Set this to run tests on HTTP1 by default
1717
ASTRA_USE_HTTP1=
18+
19+
# Set this to use the native fetch instead of fetch-h2
20+
ASTRA_USE_FETCH=

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ module.exports = {
1919
'caughtErrorsIgnorePattern': '^_',
2020
},
2121
],
22+
'@typescript-eslint/no-var-requires': 'off',
2223
},
2324
};

etc/astra-db-ts.api.md

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
55
```ts
66

7-
import { context as context_2 } from 'fetch-h2';
8-
import { Response as Response_2 } from 'fetch-h2';
97
import TypedEmitter from 'typed-emitter';
108

119
// @public
@@ -378,19 +376,7 @@ export interface DataAPIErrorDescriptor {
378376
}
379377

380378
// @public
381-
export interface DataAPIHttp1Options {
382-
keepAlive?: boolean;
383-
keepAliveMS?: number;
384-
maxFreeSockets?: number;
385-
maxSockets?: number;
386-
}
387-
388-
// @public
389-
export interface DataAPIHttpOptions {
390-
http1?: DataAPIHttp1Options;
391-
maxTimeMS?: number;
392-
preferHttp2?: boolean;
393-
}
379+
export type DataAPIHttpOptions = FetchHttpClientOptions | DefaultHttpClientOptions;
394380

395381
// @public
396382
export class DataAPIResponseError extends DataAPIError {
@@ -536,6 +522,14 @@ export interface DbSpawnOptions {
536522
token?: string;
537523
}
538524

525+
// @public
526+
export interface DefaultHttpClientOptions {
527+
client?: 'default';
528+
http1?: Http1Options;
529+
maxTimeMS?: number;
530+
preferHttp2?: boolean;
531+
}
532+
539533
// @public
540534
export interface DefaultIdOptions {
541535
type: 'uuid' | 'uuidv6' | 'uuidv7' | 'objectId';
@@ -587,10 +581,10 @@ export interface DevOpsAPIErrorDescriptor {
587581

588582
// @public
589583
export class DevOpsAPIResponseError extends DevOpsAPIError {
590-
// Warning: (ae-forgotten-export) The symbol "ResponseWithBody" needs to be exported by the entry point index.d.ts
584+
// Warning: (ae-forgotten-export) The symbol "ResponseInfo" needs to be exported by the entry point index.d.ts
591585
//
592586
// @internal
593-
constructor(resp: ResponseWithBody, data: Record<string, any> | undefined);
587+
constructor(resp: ResponseInfo, data: Record<string, any> | undefined);
594588
readonly errors: DevOpsAPIErrorDescriptor[];
595589
readonly raw: CuratedAPIResponse;
596590
readonly status: number;
@@ -616,6 +610,12 @@ export class DevOpsUnexpectedStateError extends DevOpsAPIError {
616610
export interface DropCollectionOptions extends WithTimeout, WithNamespace {
617611
}
618612

613+
// @public
614+
export interface FetchHttpClientOptions {
615+
client: 'fetch';
616+
maxTimeMS?: number;
617+
}
618+
619619
// @public
620620
export type Filter<Schema extends SomeDoc> = {
621621
[K in keyof NoId<Schema>]?: FilterExpr<NoId<Schema>[K]>;
@@ -767,6 +767,14 @@ export interface GuaranteedUpdateOptions<N extends number> {
767767
modifiedCount: N;
768768
}
769769

770+
// @public
771+
export interface Http1Options {
772+
keepAlive?: boolean;
773+
keepAliveMS?: number;
774+
maxFreeSockets?: number;
775+
maxSockets?: number;
776+
}
777+
770778
// @public
771779
export type IdOf<TSchema> = TSchema extends {
772780
_id: infer Id;

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@datastax/astra-db-ts",
3-
"version": "1.1.0",
3+
"version": "1.1.1-0",
44
"description": "Astra DB TS Client",
55
"contributors": [
66
"Kavin Gupta (https://github.com/toptobes)",

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface DataAPIRequestInfo {
3939
timeoutManager: TimeoutManager;
4040
}
4141

42-
type ExecuteCommandOptions = {
42+
interface ExecuteCommandOptions {
4343
collection?: string;
4444
namespace?: string;
4545
}
@@ -53,10 +53,7 @@ export class DataAPIHttpClient extends HttpClient {
5353
readonly #props: HTTPClientOptions & WithNamespace;
5454

5555
constructor(props: HTTPClientOptions & WithNamespace) {
56-
super({
57-
...props,
58-
mkAuthHeader: (token) => ({ [DEFAULT_DATA_API_AUTH_HEADER]: token }),
59-
});
56+
super(props, mkAuthHeader);
6057
this.namespace = props.namespace;
6158
this.#props = props;
6259
}
@@ -195,3 +192,7 @@ export function reviver(_: string, value: any): any {
195192
}
196193
return value;
197194
}
195+
196+
function mkAuthHeader(token: string): Record<string, any> {
197+
return { [DEFAULT_DATA_API_AUTH_HEADER]: token };
198+
}

src/api/devops-api-http-client.ts renamed to src/api/clients/devops-api-http-client.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
// limitations under the License.
1414
// noinspection ExceptionCaughtLocallyJS
1515

16-
import { hrTimeMs, HttpClient } from '@/src/api/http-client';
17-
import { APIResponse, HTTPClientOptions, HttpMethodStrings } from '@/src/api/types';
16+
import { hrTimeMs, HttpClient } from '@/src/api/clients/http-client';
1817
import { DevOpsAPIResponseError, DevOpsAPITimeoutError, DevOpsUnexpectedStateError } from '@/src/devops/errors';
1918
import { AdminBlockingOptions } from '@/src/devops/types';
2019
import { TimeoutManager, TimeoutOptions } from '@/src/api/timeout-managers';
@@ -25,6 +24,7 @@ import {
2524
AdminCommandStartedEvent,
2625
AdminCommandSucceededEvent,
2726
} from '@/src/devops';
27+
import { HTTPClientOptions, HttpMethodStrings } from '@/src/api/clients/types';
2828

2929
/**
3030
* @internal
@@ -36,33 +36,29 @@ export interface DevOpsAPIRequestInfo {
3636
params?: Record<string, any>,
3737
}
3838

39-
/**
40-
* @internal
41-
*/
42-
export interface LongRunningRequestInfo {
43-
id: string | ((resp: APIResponse) => string),
39+
interface LongRunningRequestInfo {
40+
id: string | ((resp: DevopsAPIResponse) => string),
4441
target: string,
4542
legalStates: string[],
4643
defaultPollInterval: number,
4744
options: AdminBlockingOptions | undefined,
4845
}
4946

47+
interface DevopsAPIResponse {
48+
data?: Record<string, any>,
49+
headers: Record<string, string>,
50+
status: number,
51+
}
52+
5053
/**
5154
* @internal
5255
*/
5356
export class DevOpsAPIHttpClient extends HttpClient {
54-
constructor(props: HTTPClientOptions) {
55-
super({
56-
...props,
57-
mkAuthHeader: (token) => ({ [DEFAULT_DEVOPS_API_AUTH_HEADER]: `Bearer ${token}` }),
58-
fetchCtx: {
59-
...props.fetchCtx,
60-
preferred: props.fetchCtx.http1,
61-
},
62-
});
57+
constructor(opts: HTTPClientOptions) {
58+
super(opts, mkAuthHeader);
6359
}
6460

65-
public async request(req: DevOpsAPIRequestInfo, options: TimeoutOptions | undefined, started: number = 0): Promise<APIResponse> {
61+
public async request(req: DevOpsAPIRequestInfo, options: TimeoutOptions | undefined, started: number = 0): Promise<DevopsAPIResponse> {
6662
const isLongRunning = started !== 0;
6763

6864
try {
@@ -80,6 +76,7 @@ export class DevOpsAPIHttpClient extends HttpClient {
8076
method: req.method,
8177
params: req.params,
8278
data: JSON.stringify(req.data),
79+
forceHttp1: true,
8380
timeoutManager,
8481
});
8582

@@ -111,7 +108,7 @@ export class DevOpsAPIHttpClient extends HttpClient {
111108
}
112109
}
113110

114-
public async requestLongRunning(req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo): Promise<APIResponse> {
111+
public async requestLongRunning(req: DevOpsAPIRequestInfo, info: LongRunningRequestInfo): Promise<DevopsAPIResponse> {
115112
const timeoutManager = this._mkTimeoutManager(info.options?.maxTimeMS);
116113
const isLongRunning = info?.options?.blocking !== false;
117114

@@ -186,6 +183,10 @@ export class DevOpsAPIHttpClient extends HttpClient {
186183

187184
private _mkTimeoutManager(timeout: number | undefined) {
188185
timeout ??= this.fetchCtx.maxTimeMS ?? (12 * 60 * 1000);
189-
return new TimeoutManager(timeout, (info) => new DevOpsAPITimeoutError(info.url, timeout));
186+
return new TimeoutManager(timeout, (url) => new DevOpsAPITimeoutError(url, timeout));
190187
}
191188
}
189+
190+
function mkAuthHeader(token: string) {
191+
return { [DEFAULT_DEVOPS_API_AUTH_HEADER]: `Bearer ${token}` };
192+
}

src/api/http-client.ts renamed to src/api/clients/http-client.ts

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
// limitations under the License.
1414

1515
import { CLIENT_USER_AGENT, RAGSTACK_REQUESTED_WITH } from '@/src/api/constants';
16-
import { HTTPRequestInfo, InternalFetchCtx, InternalHTTPClientOptions, ResponseWithBody } from '@/src/api/types';
1716
import { Caller, DataAPIClientEvents } from '@/src/client';
1817
import TypedEmitter from 'typed-emitter';
19-
import { TimeoutError } from 'fetch-h2';
18+
import { FetchCtx, ResponseInfo } from '@/src/api/fetch/types';
19+
import { AuthHeaderFactory, HTTPClientOptions, HTTPRequestInfo } from '@/src/api/clients/types';
2020

2121
/**
2222
* @internal
@@ -25,11 +25,11 @@ export abstract class HttpClient {
2525
readonly baseUrl: string;
2626
readonly emitter: TypedEmitter<DataAPIClientEvents>;
2727
readonly monitorCommands: boolean;
28-
readonly fetchCtx: InternalFetchCtx;
28+
readonly fetchCtx: FetchCtx;
2929
readonly #applicationToken: string;
3030
readonly baseHeaders: Record<string, any>;
3131

32-
protected constructor(options: InternalHTTPClientOptions) {
32+
protected constructor(options: HTTPClientOptions, mkAuthHeader: AuthHeaderFactory) {
3333
this.#applicationToken = options.applicationToken;
3434
this.baseUrl = options.baseUrl;
3535
this.emitter = options.emitter;
@@ -40,20 +40,20 @@ export abstract class HttpClient {
4040
this.baseUrl += '/' + options.baseApiPath;
4141
}
4242

43-
this.baseHeaders = options.mkAuthHeader?.(this.#applicationToken) ?? {};
43+
this.baseHeaders = mkAuthHeader?.(this.#applicationToken) ?? {};
4444
}
4545

4646
public get applicationToken(): string {
4747
return this.#applicationToken;
4848
}
4949

50-
protected async _request(info: HTTPRequestInfo): Promise<ResponseWithBody> {
50+
protected async _request(info: HTTPRequestInfo): Promise<ResponseInfo> {
5151
if (this.fetchCtx.closed.ref) {
5252
throw new Error('Can\'t make requests on a closed client');
5353
}
5454

55-
if (info.timeoutManager.msRemaining <= 0) {
56-
throw info.timeoutManager.mkTimeoutError(info);
55+
if (info.timeoutManager.msRemaining() <= 0) {
56+
throw info.timeoutManager.mkTimeoutError(info.url);
5757
}
5858

5959
const params = info.params ?? {};
@@ -63,23 +63,14 @@ export abstract class HttpClient {
6363
? `${info.url}?${new URLSearchParams(params).toString()}`
6464
: info.url;
6565

66-
try {
67-
const resp = await this.fetchCtx.preferred.fetch(url, {
68-
body: info.data as string,
69-
method: info.method,
70-
timeout: info.timeoutManager.msRemaining,
71-
headers: this.baseHeaders,
72-
}) as ResponseWithBody;
73-
74-
resp.body = await resp.text();
75-
76-
return resp;
77-
} catch (e) {
78-
if (e instanceof TimeoutError) {
79-
throw info.timeoutManager.mkTimeoutError(info);
80-
}
81-
throw e;
82-
}
66+
return await this.fetchCtx.ctx.fetch({
67+
url: url,
68+
body: info.data,
69+
method: info.method,
70+
timeoutManager: info.timeoutManager,
71+
headers: this.baseHeaders,
72+
forceHttp1: info.forceHttp1,
73+
});
8374
}
8475
}
8576

src/api/clients/types.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type TypedEmitter from 'typed-emitter';
2+
import type { DataAPICommandEvents } from '@/src/data-api';
3+
import type { FetchCtx, HttpMethods } from '@/src/api';
4+
import type { TimeoutManager } from '@/src/api/timeout-managers';
5+
6+
/**
7+
* @internal
8+
*/
9+
export interface HTTPClientOptions {
10+
baseUrl: string,
11+
baseApiPath?: string,
12+
applicationToken: string,
13+
emitter: TypedEmitter<DataAPICommandEvents>,
14+
monitorCommands: boolean,
15+
fetchCtx: FetchCtx,
16+
}
17+
18+
/**
19+
* @internal
20+
*/
21+
export type AuthHeaderFactory = (token: string) => Record<string, any>;
22+
23+
/**
24+
* @internal
25+
*/
26+
export type HttpMethodStrings = typeof HttpMethods[keyof typeof HttpMethods];
27+
28+
/**
29+
* @internal
30+
*/
31+
export interface HTTPRequestInfo {
32+
url: string,
33+
data?: string,
34+
params?: Record<string, string>,
35+
method: HttpMethodStrings,
36+
timeoutManager: TimeoutManager,
37+
forceHttp1?: boolean,
38+
}

0 commit comments

Comments
 (0)