Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): OpenAPI spec update #4

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 114 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Official Dub.co Typescript SDK
# Dub Node API Library

[![NPM version](https://img.shields.io/npm/v/dub.svg)](https://npmjs.org/package/dub)

This library provides convenient access to the Dub REST API from server-side TypeScript or JavaScript.

The API documentation can be found [here](https://dub.co/api).
The API documentation can be found [here](https://dub.co/help).

## Installation

Expand All @@ -23,16 +23,11 @@ The full API of this library can be found in [api.md](https://www.github.com/dub
import Dub from 'dub';

const dub = new Dub({
token: process.env.DUB_API_KEY, // default setting, can be omitted
apiKey: process.env['DUB_API_KEY'], // This is the default and can be omitted
});

async function main() {
const link = await dub.link.create({
projectSlug: 'xxx'
url: 'https://google.com'
})

console.log(link) // log out the created link
const projectDetails = await dub.projects.retrieve('REPLACE_ME');
}

main();
Expand All @@ -47,16 +42,11 @@ This library includes TypeScript definitions for all request params and response
import Dub from 'dub';

const dub = new Dub({
token: process.env.DUB_API_KEY, // default setting, can be omitted
apiKey: process.env['DUB_API_KEY'], // This is the default and can be omitted
});

async function main() {
const link: Dub.Link = await dub.link.create({
projectSlug: 'xxx'
url: 'https://google.com'
})

console.log(link) // log out the created link
const projectDetails: Dub.ProjectDetails = await dub.projects.retrieve('REPLACE_ME');
}

main();
Expand Down Expand Up @@ -142,6 +132,114 @@ On timeout, an `APIConnectionTimeoutError` is thrown.

Note that requests which time out will be [retried twice by default](#retries).

## Advanced Usage

### Accessing raw Response data (e.g., headers)

The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.

You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data.

<!-- prettier-ignore -->
```ts
const dub = new Dub();

const response = await dub.projects.retrieve('REPLACE_ME').asResponse();
console.log(response.headers.get('X-My-Header'));
console.log(response.statusText); // access the underlying Response object

const { data: projectDetails, response: raw } = await dub.projects.retrieve('REPLACE_ME').withResponse();
console.log(raw.headers.get('X-My-Header'));
console.log(projectDetails);
```

## Customizing the fetch client

By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments.

If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment,
(for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`),
add the following import before your first import `from "Dub"`:

```ts
// Tell TypeScript and the package to use the global web fetch instead of node-fetch.
// Note, despite the name, this does not add any polyfills, but expects them to be provided if needed.
import 'dub/shims/web';
import Dub from 'dub';
```

To do the inverse, add `import "dub/shims/node"` (which does import polyfills).
This can also be useful if you are getting the wrong TypeScript types for `Response` - more details [here](https://github.com/dubinc/dub-node/tree/main/src/_shims#readme).

You may also provide a custom `fetch` function when instantiating the client,
which can be used to inspect or alter the `Request` or `Response` before/after each request:

```ts
import { fetch } from 'undici'; // as one example
import Dub from 'dub';

const client = new Dub({
fetch: (url: RequestInfo, init?: RequestInfo): Response => {
console.log('About to make request', url, init);
const response = await fetch(url, init);
console.log('Got response', response);
return response;
},
});
```

Note that if given a `DEBUG=true` environment variable, this library will log all requests and responses automatically.
This is intended for debugging purposes only and may change in the future without notice.

## Configuring an HTTP(S) Agent (e.g., for proxies)

By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests.

If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example:

<!-- prettier-ignore -->
```ts
import http from 'http';
import HttpsProxyAgent from 'https-proxy-agent';

// Configure the default for all requests:
const dub = new Dub({
httpAgent: new HttpsProxyAgent(process.env.PROXY_URL),
});

// Override per-request:
await dub.projects.retrieve('REPLACE_ME', {
baseURL: 'http://localhost:8080/test-api',
httpAgent: new http.Agent({ keepAlive: false }),
})
```

## Semantic Versioning

This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:

1. Changes that only affect static types, without breaking runtime behavior.
2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_.
3. Changes that we do not expect to impact the vast majority of users in practice.

We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.

We are keen for your feedback; please open an [issue](https://www.github.com/dubinc/dub-node/issues) with questions, bugs, or suggestions.

## Requirements

TypeScript >= 4.5 is supported.

The following runtimes are supported:

- Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions.
- Deno v1.28.0 or higher, using `import Dub from "npm:dub"`.
- Bun 1.0 or later.
- Cloudflare Workers.
- Vercel Edge Runtime.
- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time).
- Nitro v2.6 or greater.

Note that React Native is not supported at this time.

If you are interested in other runtime environments, please open or upvote an issue on GitHub.
6 changes: 4 additions & 2 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,14 +952,16 @@ export const ensurePresent = <T>(value: T | null | undefined): T => {
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env: string): string | undefined => {
if (typeof process !== 'undefined') {
return process.env?.[env] ?? undefined;
return process.env?.[env]?.trim() ?? undefined;
}
if (typeof Deno !== 'undefined') {
return Deno.env?.get?.(env);
return Deno.env?.get?.(env)?.trim();
}
return undefined;
};
Expand Down
9 changes: 1 addition & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export interface ClientOptions {
*/
apiKey?: string;

projectSlug?: string | null;

/**
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
*
Expand Down Expand Up @@ -74,15 +72,13 @@ export interface ClientOptions {
/** API Client for interfacing with the Dub API. */
export class Dub extends Core.APIClient {
apiKey: string;
projectSlug: string | null;

private _options: ClientOptions;

/**
* API Client for interfacing with the Dub API.
*
* @param {string} [opts.apiKey=process.env['DUB_API_KEY'] ?? undefined]
* @param {string | null} [opts.projectSlug]
* @param {string} [opts.baseURL=process.env['DUB_BASE_URL'] ?? https://api.dub.co] - Override the default base URL for the API.
* @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
Expand All @@ -94,7 +90,6 @@ export class Dub extends Core.APIClient {
constructor({
baseURL = Core.readEnv('DUB_BASE_URL'),
apiKey = Core.readEnv('DUB_API_KEY'),
projectSlug = null,
...opts
}: ClientOptions = {}) {
if (apiKey === undefined) {
Expand All @@ -105,9 +100,8 @@ export class Dub extends Core.APIClient {

const options: ClientOptions = {
apiKey,
projectSlug,
...opts,
baseURL: baseURL ?? `https://api.dub.co`,
baseURL: baseURL || `https://api.dub.co`,
};

super({
Expand All @@ -120,7 +114,6 @@ export class Dub extends Core.APIClient {
this._options = options;

this.apiKey = apiKey;
this.projectSlug = projectSlug;
}

links: API.Links = new API.Links(this);
Expand Down
14 changes: 13 additions & 1 deletion tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('instantiate client', () => {
});

afterEach(() => {
process.env['SINK_BASE_URL'] = undefined;
process.env['DUB_BASE_URL'] = undefined;
});

test('explicit option', () => {
Expand All @@ -147,6 +147,18 @@ describe('instantiate client', () => {
const client = new Dub({ apiKey: 'My API Key' });
expect(client.baseURL).toEqual('https://example.com/from_env');
});

test('empty env variable', () => {
process.env['DUB_BASE_URL'] = ''; // empty
const client = new Dub({ apiKey: 'My API Key' });
expect(client.baseURL).toEqual('https://api.dub.co');
});

test('blank env variable', () => {
process.env['DUB_BASE_URL'] = ' '; // blank
const client = new Dub({ apiKey: 'My API Key' });
expect(client.baseURL).toEqual('https://api.dub.co');
});
});

test('maxRetries option is correctly set', () => {
Expand Down