Skip to content

Commit

Permalink
feat(hooks): support multiple hooks
Browse files Browse the repository at this point in the history
Provides optional array syntax when defining hooks, which allows users to define more than one
before or after hook.
  • Loading branch information
estrattonbailey committed Jun 24, 2021
1 parent 07e9b9e commit 0e0550d
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 23 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ const { url, status, response } = await gretch(
are good for code that needs to run on every request, like adding tracking
headers and logging errors.

Hooks should be defined as an array. That way you can compose multiple hooks
per-request, and define and merge default hooks when [creating
instances](#creating-instances).

#### `before`

The `before` hook runs just prior to the request being made. You can even modify
Expand All @@ -227,24 +231,29 @@ object, and the full options object.
```js
const response = await gretch('/api/user/12', {
hooks: {
before (request, options) {
request.headers.set('Tracking-ID', 'abcde')
}
before: [
(request, options) => {
request.headers.set('Tracking-ID', 'abcde')
}
]
}
}).json()
```

#### `after`

The `after` hook has the opportunity to read the `gretchen` response. It
The `after` runs after the request has resolved and any body interface methods
have been called. It has the opportunity to read the `gretchen` response. It
_cannot_ modify it. This is mostly useful for logging.

```js
const response = await gretch('/api/user/12', {
hooks: {
after ({ url, status, data, error }) {
sentry.captureMessage(`${url} returned ${status}`)
}
after: [
({ url, status, data, error }, options) => {
sentry.captureMessage(`${url} returned ${status}`)
}
]
}
}).json()
```
Expand Down
15 changes: 11 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ export type GretchResponse<T = DefaultGretchResponse, A = DefaultGretchError> =
response: Response
}

export type GretchBeforeHook = (request: Request, opts: GretchOptions) => void
export type GretchAfterHook = (
response: GretchResponse,
opts: GretchOptions
) => void

export type GretchHooks = {
before?: (request: Request, opts: GretchOptions) => void
after?: (response: GretchResponse, opts: GretchOptions) => void
before?: GretchBeforeHook | GretchBeforeHook[]
after?: GretchAfterHook | GretchAfterHook[]
}

export type GretchOptions = {
Expand Down Expand Up @@ -97,7 +103,7 @@ export function gretch<T = DefaultGretchResponse, A = DefaultGretchError> (
baseURL !== undefined ? normalizeURL(url, { baseURL }) : url
const request = new Request(normalizedURL, options)

if (hooks.before) hooks.before(request, opts)
if (hooks.before) [].concat(hooks.before).forEach(hook => hook(request, opts))

const fetcher = () =>
timeout
Expand Down Expand Up @@ -152,7 +158,8 @@ export function gretch<T = DefaultGretchResponse, A = DefaultGretchError> (
response
}

if (hooks.after) hooks.after(res, opts)
if (hooks.after)
[].concat(hooks.after).forEach(hook => hook({ ...res }, opts))

return res
}
Expand Down
6 changes: 4 additions & 2 deletions lib/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ export function merge (
headersToObj(new Headers(a[k])),
headersToObj(new Headers(v))
)
} else if (v.pop) {
} else if (v.pop && a[k].pop) {
c[k] = [...(a[k] || []), ...v]
} else {
} else if (typeof a[k] === 'object' && !a[k].pop) {
c[k] = merge(a[k], v)
} else {
c[k] = v
}
} else {
c[k] = v
Expand Down
19 changes: 14 additions & 5 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,16 +267,25 @@ export default (test, assert) => {
await gretch(`http://127.0.0.1:${port}`, {
timeout: 50000,
hooks: {
before (request) {
before (request, opts) {
assert(request.url)
assert(opts.timeout)
hooks++
},
after ({ status }) {
hooks++
}
after: [
(response, opts) => {
assert(response.status)
assert(opts.timeout)
hooks++
},
() => {
hooks++
}
]
}
}).json()

assert(hooks === 2)
assert(hooks === 3)

server.close()

Expand Down
18 changes: 13 additions & 5 deletions test/merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,30 @@ export default (test, assert) => {
assert.equal(o.headers['x-out'], 'out')
})

test('overwrites values', () => {
test('overwrites mixed values', () => {
const o = merge(
{
timeout: 100,
retry: {
attempts: 3
retry: false,
hooks: {
after () {}
}
},
{
timeout: 200,
retry: false
retry: {
attempts: 3
},
hooks: {
after: [() => {}]
}
}
)

assert.equal(o.timeout, 200)
assert.equal(o.retry, false)
// @ts-ignore
assert.equal(o.retry.attempts, 3)
assert(Array.isArray(o.hooks.after))
})

test('merges hooks', () => {
Expand Down

0 comments on commit 0e0550d

Please sign in to comment.