@procore/core-http
Version:
A HTTP Client
276 lines (197 loc) • 10.7 kB
Markdown
# `@procore/core-http`
A simple HTTP client. Built on top of [up-fetch]!
```bash
yarn add @procore/core-http
```
This simple wrapper over native fetch makes interacting with the Procore API a little easier. By default, it injects a CSRF token from known places when working from inside of an Micro Front End, or in a Hydra Client, but also provides customization and an extension mechanism.
## Usage
<details>
<summary>Simple Example</summary>
```ts
import { createClient } from '@procore/core-http'
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
})
function ExampleRequest() {
client('/some-url-here')
.then((data) => {
// Contains the actual result. No need to call response.json() or check response.ok
console.log(data)
})
.catch((err) => {
console.error(err)
})
}
```
</details>
<details>
<summary>Customize options, such as <em>baseUrl</em></summary>
```ts
import { createClient } from '@procore/core-http'
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
defaults: () => ({
baseUrl: 'https://www.example.com', // uses this baseUrl for every request.
}),
})
function ExampleRequest() {
client('/some-url-here') // request will be 'https://www.example.com/some-url-here'
.then((data) => {
// Contains the actual result. No need to call response.json() or check response.ok
console.log(data)
})
.catch((err) => {
console.error(err)
})
}
// Or, one can override an option on a request-by-request basis:
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
systemEvents, // pass your app's instance so the host receives events with a more accurate source
})
client('/some-url-here', { baseUrl: 'https://www.example.com' })
```
</details>
## API
### `createClient({ errorReportingApiKey, defaults?, plugins?, fetchFn?, defaultErrorHandling?, systemEvents? })`
```ts
createClient({
errorReportingApiKey: string,
defaults: () => RequestOptions,
plugins: Array<Plugin>,
fetchFn: typeof fetch,
defaultErrorHandling: boolean,
systemEvents: SystemEvents,
}): client
```
#### `systemEvents` - optional _(default: `new SystemEvents('core-http')`)_
The event bus used to publish `UNCAUGHT_EXCEPTION` when requests fail. **Pass your app's `SystemEvents` instance** so the host receives events with a more accurate source as part of its payload when `UNCAUGHT_EXCEPTION` is dispatched. If omitted, a new instance of `SystemEvents` is created with source `core-http`.
#### `defaults` - optional _(default: `() => ({})`)_
A function that returns the default request parameters that should be used on every request. It maybe be a synchronous or asynchronous function.
Equivalent to the [getDefaultOptions][up] function passed to the [up][up] function in [up-fetch][up-fetch]. Refer to the [getDefaultOptions][up] API docs for all of the options.
#### `plugins` - optional _(default: `[]`)_
A collection of plugins to augment the behavior of each request. Refer to the ["Plugins"](#plugins) below for more details.
#### `fetchFn` - optional _(default: `fetch`)_
The function that is used to actually make the fetch calls. Equivalent to the [fetch][up] parameter in the [up][up] function in [up-fetch][up-fetch]. The default should work for most cases. The only reason to consider overriding it is perhaps for unit tests, although there are better options.
#### `defaultErrorHandling` - optional _(default: `true`)_
When `true`, `createClient` will publish a `SystemEventNames.UNCAUGHT_EXCEPTION` event when a request fails. To opt out per request, pass `defaultErrorHandling: false` in request options. When `false`, `createClient` will always throw errors and will not publish the system event.
#### `errorReportingApiKey` - required
This value is included in the `UNCAUGHT_EXCEPTION` event payload as `errorReportingApiKey`. When it is an empty string, default error reporting is effectively disabled (exceptions are still thrown).
### `isResponseError(error: unknown): boolean`
Returns `true` when the error is a response error. Exported from [up-fetch][response-error].
```ts
import { isResponseError } from '@procore/core-http'
async function makeRequest() {
try {
// makes a core-http fetch call
} catch (error: unknown) {
if (isResponseError(error)) {
// handle the strongly-typed response error
} else {
throw error // re-throw for the new handler, or handle it differently
}
}
}
```
### `client(url, options?)`
The function returned from `createClient`. Used to make fetch requests.
```ts
function upfetch(
url: string | URL | Request,
options?: FetcherOptions
): Promise<any>
```
#### `url` - required
The request object. This is be a `string`, `URL`, or `Request` object.
#### `options` - optional _(default: `{}`)_
Additional options to add to this specific request. Note that any properties provided here will override the defaults.
Additional information about the available options can be found in the [up-fetch][client] docs.
### `isValidationError(error: unknown): boolean`
Returns `true` when the error is a schema validation error. Exported from [up-fetch][validation-error].
```ts
import { isValidationError } from '@procore/core-http'
async function makeRequest() {
try {
// makes a core-http fetch call
} catch (error: unknown) {
if (isValidationError(error)) {
// handle the strongly-typed validation error
} else {
throw error // re-throw for the new handler, or handle it differently
}
}
}
```
### `request` _(deprecated)_
Makes a fetch request, with no additional handling. It is **strongly** recommended to use `createClient` instead of this method.
```ts
request(url: string, requestParams: RequestParams)
```
#### `url` - required
The resource to fetch
#### `requestParams` - optional _(default: {})_
Used to augment the resulting `fetch` call with additional configuration, such as `method`, `baseUrl`, and `errorReportingApiKey`.
- **`errorReportingApiKey`** - optional. When non-empty, enables error reporting for failed requests. Defaults to `''` (disabled; no reporting).
- **`systemEvents`** - optional. Specifies which `SystemEvents` instance/event bus to use so the host receives events with a more accurate source as part of its payload when `UNCAUGHT_EXCEPTION` is dispatched. If omitted, a new instance of `SystemEvents` is created with source `core-http`.
```ts
import { request } from '@procore/core-http'
function ExampleRequest() {
request('/some-url-here')
.then((response) => console.log(response))
.catch((err) => console.error(err))
}
```
### `requestJSON` _(deprecated)_
Fetches a resource, and attempts to cast the parsed JSON response as the specified type. It is **strongly** recommended to use `createClient` instead of this method, which supports both schema validation and error handling.
```ts
requestJSON<T>(url: string, requestParams: RequestParams)
```
#### `url` - required
The resource to fetch
#### `requestParams` - optional _(default: {})_
Used to augment the resulting `fetch` call with additional configuration, such as `method` and `baseUrl`.
```ts
import { requestJSON } from '@procore/core-http'
function ExampleRequest() {
requestJSON('/some-url-here.json')
.then((response) => console.log(response))
.catch((err) => console.error(err))
}
```
### Plugins
Plugins provide an opportunity augment the behavior that occurs with each request. Examples of plugin behavior include:
- Adding a header to every request.
- Emit an event based on an 4xx or 5xx error response.
- Display a modal based on an error response.
- Instrument requests and responses with Open Telemetry or logging.
The plugin interface is as follows:
```ts
interface Plugin {
onError?: (error, request) => void
onRequest?: (request) => void
onSuccess?: (data, request) => void
}
```
`onRequest` functions are run when a request is initiated. `onSuccess` is called when a request successfully returns. Finally, `onError` is called when an error response is received. Refer to the ["Lifecycle Hooks"][lifecycle-hooks] section in the [up-fetch][lifecycle-hooks] docs for more details about each method.
There are a few caveats to understand when working with lifecycle hooks:
- Order matters! Lifecycle hooks execute in the following order:
- Lifecycle hooks added to default options
- Lifecycle hooks in each plugin, in order
- Lifecycle hooks added to an individual request
- Plugins only need to implement the lifecycle hooks that they need. For example, when implementing a plugin that only handle error responses, you can omit the `onRequest` and `onSuccess` methods.
- The `defaultErrorHandling` option controls how errors are handled. By default, errors are still thrown even if you provide an `onError` handler; use `onError` for logging or other side effects rather than to suppress the error.
### Additional Considerations
`createClient` opens up all of the functionality of [up-fetch][up-fetch], while providing some Procore-specific defaults. Features that you should consider using include:
- [Simple query parameters][simple-query-parameters]: the library supports specifying query parameters via a simple `{params: {}}` property passed to your request.
- [Automatic body handling][automatic-body-handling]: the library supports automatic serialization of the body on `POST` calls.
- [Schema validation][schema-validation]: the library supports [Standard Schema][standard-schema]. This will not only validate the responses that you receive from a fetch call, but also return the data in the validate type (no more type casting!).
[automatic-body-handling]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#%EF%B8%8F-automatic-body-handling
[client]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#upfetchurl-options
[lifecycle-hooks]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#%EF%B8%8F-lifecycle-hooks
[response-error]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#-responseerror
[schema-validation]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#%EF%B8%8F-schema-validation
[simple-query-parameters]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#%EF%B8%8F-simple-query-parameters
[standard-schema]: https://github.com/standard-schema/standard-schema
[up-fetch]: https://github.com/L-Blondy/up-fetch
[up]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#upfetch-getdefaultoptions
[validation-error]: https://github.com/L-Blondy/up-fetch?tab=readme-ov-file#-validationerror