Skip to content

Commit 5ef710a

Browse files
authored
Further non-standard environment compatibility fixes/patches/improvements (#45)
Made client default to fetch if fetch-h2 fails and it isn't forced to be the default client Allow users to pass in a custom fetcher Examples folder Further documentation More tests
1 parent f7d87e4 commit 5ef710a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2749
-253
lines changed

README.md

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
- [Working with Dates](#working-with-dates)
1212
- [Working with ObjectIds and UUIDs](#working-with-objectids-and-uuids)
1313
- [Monitoring/logging](#monitoringlogging)
14-
- [Non-standard runtime support](#non-standard-runtime-support)
14+
- [Non-standard environment support](#non-standard-environment-support)
15+
- [HTTP/2 with minification](#http2-with-minification)
16+
- [Browser support](#browser-support)
1517

1618
## Quickstart
1719

@@ -331,30 +333,77 @@ client.on('commandFailed', (event) => {
331333
})();
332334
```
333335

334-
## Non-standard runtime support
336+
## Non-standard environment support
335337

336-
`astra-db-ts` is designed foremost to work in Node.js environments, and it's not supported in browsers. It will work
337-
in edge runtimes and other non-node environments as well, though it'll use the native `fetch` API for HTTP requests,
338-
as opposed to `fetch-h2` which provides extended HTTP/2 and HTTP/1.1 support for performance.
338+
`astra-db-ts` is designed foremost to work in Node.js environments.
339339

340-
On Node (exactly node; not Bun or Deno with node compat on) environments, it'll use `fetch-h2` by default; on others,
341-
it'll use `fetch`. You can explicitly set the fetch implementation when instantiating the client:
340+
It will work in edge runtimes and other non-node environments as well, though it'll use the native `fetch` API for HTTP
341+
requests, as opposed to `fetch-h2` which provides extended HTTP/2 and HTTP/1.1 support for performance.
342+
343+
By default, it'll attempt to use `fetch-h2` if available, and fall back to `fetch` if not available in that environment.
344+
You can explicitly force the fetch implementation when instantiating the client:
342345

343346
```typescript
344347
import { DataAPIClient } from '@datastax/astra-db-ts';
345348

346349
const client = new DataAPIClient('*TOKEN*', {
347-
httpOptions: {
348-
client: 'fetch', // or 'default' for fetch-h2
349-
},
350+
httpOptions: { client: 'fetch' },
350351
});
351352
```
352353

353-
Note that setting the `httpOptions` implicitly sets the fetch implementation to default (fetch-h2),
354-
so you'll need to set it explicitly if you want to use the native fetch implementation.
354+
There are four different behaviours for setting the client:
355+
- Not setting the `httpOptions` at all
356+
- This will attempt to use `fetch-h2` if available, and fall back to `fetch` if not available
357+
- `client: 'default'` or `client: undefined` (or unset)
358+
- This will attempt to use `fetch-h2` if available, and throw an error if not available
359+
- `client: 'fetch'`
360+
- This will always use the native `fetch` API
361+
- `client: 'custom'`
362+
- This will allow you to pass a custom `Fetcher` implementation to the client
355363

356364
On some environments, such as Cloudflare Workers, you may additionally need to use the events
357365
polyfill for the client to work properly (i.e. `npm i events`). Cloudflare's node-compat won't
358366
work here.
359367

360368
Check out the `examples/` subdirectory for some non-standard runtime examples with more info.
369+
370+
### HTTP/2 with minification
371+
372+
Due to the variety of different runtimes JS can run in, `astra-db-ts` does its best to be as flexible as possible.
373+
Unfortunately however, because we need to dynamically require the `fetch-h2` module to test whether it works, the
374+
dynamic import often breaks in minified environments, even if the runtime properly supports HTTP/2.
375+
376+
There is a simple workaround however, consisting of the following steps, if you really want to use HTTP/2:
377+
1. Install `fetch-h2` as a dependency (`npm i fetch-h2`)
378+
2. Import the `fetch-h2` module in your code as `fetchH2` (i.e. `import * as fetchH2 from 'fetch-h2'`)
379+
3. Set the `httpOptions.fetchH2` option to the imported module when instantiating the client
380+
381+
```typescript
382+
import { DataAPIClient } from '@datastax/astra-db-ts';
383+
import * as fetchH2 from 'fetch-h2';
384+
385+
const client = new DataAPIClient('*TOKEN*', {
386+
httpOptions: { fetchH2 },
387+
});
388+
```
389+
390+
This way, the dynamic import is avoided, and the client will work in minified environments.
391+
392+
Note this is not required if you don't explicitly need HTTP/2 support, as the client will default
393+
to the native fetch implementation instead if importing fails.
394+
395+
(But keep in mind this defaulting will only happen if `httpOptions` is not set at all).
396+
397+
(See `examples/http2-when-minified` for a full example of this workaround in action.)
398+
399+
### Browser support
400+
401+
The Data API itself does not natively support browsers, so `astra-db-ts` isn't technically supported in browsers either.
402+
403+
However, if, for some reason, you really want to use this in a browser, you can probably do so by installing the
404+
`events` polyfill and setting up a [CORS proxy](https://github.com/Rob--W/cors-anywhere) to forward requests to the Data API.
405+
406+
But keep in mind that this is not officially supported, and may be very insecure if you're encoding sensitive
407+
data into the browser client.
408+
409+
(See `examples/browser` for a full example of this workaround in action.)

etc/astra-db-ts.api.md

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,8 @@ export abstract class CumulativeDataAPIError extends DataAPIResponseError {
307307
readonly partialResult: unknown;
308308
}
309309

310-
// @public
311-
export interface CuratedAPIResponse {
312-
body?: string;
313-
headers: Record<string, any>;
314-
httpVersion: 1 | 2;
315-
status: number;
316-
statusText: string;
317-
url: string;
318-
}
310+
// @public @deprecated
311+
export type CuratedAPIResponse = FetcherResponseInfo;
319312

320313
// @public
321314
export type CurrentDate<Schema> = {
@@ -381,8 +374,10 @@ export interface DataAPIErrorDescriptor {
381374
readonly message?: string;
382375
}
383376

377+
// Warning: (ae-forgotten-export) The symbol "CustomHttpClientOptions" needs to be exported by the entry point index.d.ts
378+
//
384379
// @public
385-
export type DataAPIHttpOptions = FetchHttpClientOptions | DefaultHttpClientOptions;
380+
export type DataAPIHttpOptions = DefaultHttpClientOptions | FetchHttpClientOptions | CustomHttpClientOptions;
386381

387382
// @public
388383
export class DataAPIResponseError extends DataAPIError {
@@ -531,6 +526,7 @@ export interface DbSpawnOptions {
531526
// @public
532527
export interface DefaultHttpClientOptions {
533528
client?: 'default';
529+
fetchH2?: any;
534530
http1?: Http1Options;
535531
maxTimeMS?: number;
536532
preferHttp2?: boolean;
@@ -587,12 +583,10 @@ export interface DevOpsAPIErrorDescriptor {
587583

588584
// @public
589585
export class DevOpsAPIResponseError extends DevOpsAPIError {
590-
// Warning: (ae-forgotten-export) The symbol "ResponseInfo" needs to be exported by the entry point index.d.ts
591-
//
592586
// @internal
593-
constructor(resp: ResponseInfo, data: Record<string, any> | undefined);
587+
constructor(resp: FetcherResponseInfo, data: Record<string, any> | undefined);
594588
readonly errors: DevOpsAPIErrorDescriptor[];
595-
readonly raw: CuratedAPIResponse;
589+
readonly raw: FetcherResponseInfo;
596590
readonly status: number;
597591
}
598592

@@ -616,6 +610,41 @@ export class DevOpsUnexpectedStateError extends DevOpsAPIError {
616610
export interface DropCollectionOptions extends WithTimeout, WithNamespace {
617611
}
618612

613+
// @public
614+
export class FailedToLoadDefaultClientError extends Error {
615+
// @internal
616+
constructor(rootCause: Error);
617+
readonly rootCause: Error;
618+
}
619+
620+
// @public
621+
export interface Fetcher {
622+
close?(): Promise<void>;
623+
fetch(info: FetcherRequestInfo): Promise<FetcherResponseInfo>;
624+
}
625+
626+
// @public
627+
export interface FetcherRequestInfo {
628+
body: string | undefined;
629+
forceHttp1: boolean | undefined;
630+
headers: Record<string, string>;
631+
method: 'DELETE' | 'GET' | 'POST';
632+
mkTimeoutError: () => Error;
633+
timeout: number;
634+
url: string;
635+
}
636+
637+
// @public
638+
export interface FetcherResponseInfo {
639+
additionalAttributes?: Record<string, any>;
640+
body?: string;
641+
headers: Record<string, any>;
642+
httpVersion: 1 | 2;
643+
status: number;
644+
statusText: string;
645+
url: string;
646+
}
647+
619648
// @public
620649
export interface FetchHttpClientOptions {
621650
client: 'fetch';

examples/browser/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Your Astra DB token (AstraCS:...)
2+
VITE_ASTRA_DB_TOKEN=
3+
4+
# Your Astra DB endpoint (https://<id>-<region>.apps.astra.datastax.com)
5+
VITE_ASTRA_DB_ENDPOINT=

examples/browser/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

examples/browser/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# astra-db-ts with HTTP/2 in a Minified Project
2+
3+
## Overview
4+
5+
The Data API itself does not natively support browsers, so `astra-db-ts` isn't technically supported in browsers either.
6+
7+
However, if, for some reason, you really want to use this in a browser, you can probably do so by installing the
8+
`events` polyfill and setting up a [CORS proxy](https://github.com/Rob--W/cors-anywhere) to forward requests to the Data API. If no `httpOptions` are
9+
provided, it will, by default, use the native `fetch` API (as the default `fetch-h2` isn't supported in browsers).
10+
11+
This is a simple example of how we can interact with an Astra database in a browser environment. It will list out
12+
all the collections in a given database.
13+
14+
Do keep in mind that this is not officially supported, and may be very insecure if you're encoding sensitive
15+
data into the browser client.
16+
17+
Check out the [Non-standard environment support](../../README.md#non-standard-environment-support) section
18+
in the main `README.md` for more information common to non-standard environments.
19+
20+
## Getting Started
21+
22+
### Prerequisites:
23+
24+
- Ensure you have an existing Astra Database running at [astra.datastax.com](https://astra.datastax.com/).
25+
- You'll need an API key and a database endpoint URL to get started.
26+
27+
### How to Use This Example:
28+
29+
1. Clone this repository to your local machine.
30+
31+
2. Run `npm install` to install the required dependencies.
32+
33+
3. Copy the `.env.example` file to `.env` and fill in the required values.
34+
35+
4. Run `npm run dev` to start the local development server.
36+
37+
5. Visit `http://localhost:5173` in your browser to see the example in action.
38+
39+
### Steps to Start Your Own Project:
40+
41+
1. Create a new project as you please.
42+
43+
2. Install `@datastax/astra-db-ts` by running `npm i @datastax/astra-db-ts`.
44+
45+
3. Install the `events` polyfill (if your build tool doesn't provide polyfills) by running `npm i events`.
46+
47+
4. Set up a CORS proxy to forward requests to the Data API. You can use [cors-anywhere](https://github.com/Rob--W/cors-anywhere),
48+
or any other CORS proxy of your choice.
49+
50+
5. When doing `client.db()`, prefix the endpoint URL with the CORS proxy URL as appropriate.
51+
52+
6. You should be able to use `@datastax/astra-db-ts` in your project as normal now.
53+
54+
**Please be very careful about not hard-coding credentials or sensitive data in your client-side code.**
55+
56+
## Full Code Sample
57+
58+
```ts
59+
import { DataAPIClient } from '@datastax/astra-db-ts';
60+
61+
const client = new DataAPIClient(prompt('Enter your AstraDB API key: '));
62+
const db = client.db(`${import.meta.env.CORS_PROXY_URL}${import.meta.env.ASTRA_DB_ENDPOINT}`);
63+
64+
const app = document.querySelector<HTMLDivElement>('#app')!;
65+
app.innerHTML = '<p>Loading...</p>';
66+
67+
db.listCollections().then((collections) => {
68+
app.innerHTML = `<code>${JSON.stringify(collections, null, 2)}</code>`;
69+
});
70+
```

examples/browser/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + TS</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)