Skip to content

Commit 89c72c9

Browse files
authoredAug 19, 2021
Merge pull request #23 from truework/beta
[Beta] multiple hooks, deep merge options
2 parents e9e3d14 + f4211e5 commit 89c72c9

File tree

7 files changed

+204
-19
lines changed

7 files changed

+204
-19
lines changed
 

‎README.md

+16-7
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ const { url, status, response } = await gretch(
218218
are good for code that needs to run on every request, like adding tracking
219219
headers and logging errors.
220220

221+
Hooks should be defined as an array. That way you can compose multiple hooks
222+
per-request, and define and merge default hooks when [creating
223+
instances](#creating-instances).
224+
221225
#### `before`
222226

223227
The `before` hook runs just prior to the request being made. You can even modify
@@ -227,24 +231,29 @@ object, and the full options object.
227231
```js
228232
const response = await gretch('/api/user/12', {
229233
hooks: {
230-
before (request, options) {
231-
request.headers.set('Tracking-ID', 'abcde')
232-
}
234+
before: [
235+
(request, options) => {
236+
request.headers.set('Tracking-ID', 'abcde')
237+
}
238+
]
233239
}
234240
}).json()
235241
```
236242

237243
#### `after`
238244

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

242249
```js
243250
const response = await gretch('/api/user/12', {
244251
hooks: {
245-
after ({ url, status, data, error }) {
246-
sentry.captureMessage(`${url} returned ${status}`)
247-
}
252+
after: [
253+
({ url, status, data, error }, options) => {
254+
sentry.captureMessage(`${url} returned ${status}`)
255+
}
256+
]
248257
}
249258
}).json()
250259
```

‎index.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,18 @@ import {
66
} from './lib/handleRetry'
77
import { handleTimeout } from './lib/handleTimeout'
88
import { normalizeURL } from './lib/utils'
9+
import { merge } from './lib/merge'
910

1011
export type DefaultGretchResponse = any
1112
export type DefaultGretchError = any
1213

14+
export type MergeableObject =
15+
| {
16+
[k: string]: MergeableObject
17+
}
18+
| Partial<GretchOptions>
19+
| any[]
20+
1321
export type GretchResponse<T = DefaultGretchResponse, A = DefaultGretchError> =
1422
| {
1523
url: string
@@ -26,9 +34,15 @@ export type GretchResponse<T = DefaultGretchResponse, A = DefaultGretchError> =
2634
response: Response
2735
}
2836

37+
export type GretchBeforeHook = (request: Request, opts: GretchOptions) => void
38+
export type GretchAfterHook = (
39+
response: GretchResponse,
40+
opts: GretchOptions
41+
) => void
42+
2943
export type GretchHooks = {
30-
before?: (request: Request, opts: GretchOptions) => void
31-
after?: (response: GretchResponse, opts: GretchOptions) => void
44+
before?: GretchBeforeHook | GretchBeforeHook[]
45+
after?: GretchAfterHook | GretchAfterHook[]
3246
}
3347

3448
export type GretchOptions = {
@@ -89,7 +103,7 @@ export function gretch<T = DefaultGretchResponse, A = DefaultGretchError> (
89103
baseURL !== undefined ? normalizeURL(url, { baseURL }) : url
90104
const request = new Request(normalizedURL, options)
91105

92-
if (hooks.before) hooks.before(request, opts)
106+
if (hooks.before) [].concat(hooks.before).forEach(hook => hook(request, opts))
93107

94108
const fetcher = () =>
95109
timeout
@@ -144,7 +158,8 @@ export function gretch<T = DefaultGretchResponse, A = DefaultGretchError> (
144158
response
145159
}
146160

147-
if (hooks.after) hooks.after(res, opts)
161+
if (hooks.after)
162+
[].concat(hooks.after).forEach(hook => hook({ ...res }, opts))
148163

149164
return res
150165
}
@@ -158,6 +173,6 @@ export function create (defaultOpts: GretchOptions = {}) {
158173
T = DefaultGretchResponse,
159174
A = DefaultGretchError
160175
> (url: string, opts: GretchOptions = {}): GretchInstance<T, A> {
161-
return gretch(url, { ...defaultOpts, ...opts })
176+
return gretch(url, merge(defaultOpts, opts))
162177
}
163178
}

‎lib/merge.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { GretchOptions, MergeableObject } from '../index'
2+
3+
function headersToObj (headers: Headers) {
4+
let o = {}
5+
6+
headers.forEach((v, k) => {
7+
o[k] = v
8+
})
9+
10+
return o
11+
}
12+
13+
export function merge (
14+
a: MergeableObject = {},
15+
b: MergeableObject = {}
16+
): GretchOptions {
17+
let c = { ...a }
18+
19+
for (const k of Object.keys(b)) {
20+
const v = b[k]
21+
22+
if (typeof v === 'object') {
23+
if (k === 'headers') {
24+
c[k] = merge(
25+
headersToObj(new Headers(a[k])),
26+
headersToObj(new Headers(v))
27+
)
28+
} else if (v.pop && a[k].pop) {
29+
c[k] = [...(a[k] || []), ...v]
30+
} else if (typeof a[k] === 'object' && !a[k].pop) {
31+
c[k] = merge(a[k], v)
32+
} else {
33+
c[k] = v
34+
}
35+
} else {
36+
c[k] = v
37+
}
38+
}
39+
40+
return c
41+
}

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"version": "1.2.0",
44
"description": "Making fetch happen in Typescript.",
55
"source": "index.ts",
6-
"main": "dist/gretchen.cjs.js",
7-
"modern": "dist/gretchen.esm.js",
6+
"main": "dist/gretchen.js",
7+
"modern": "dist/gretchen.modern.js",
88
"module": "dist/gretchen.esm.js",
99
"unpkg": "dist/gretchen.iife.js",
1010
"types": "dist/index.d.ts",

‎test/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require('./utils.test').default(test, assert)
99
require('./handleRetry.test').default(test, assert)
1010
require('./handleTimeout.test').default(test, assert)
1111
require('./index.test').default(test, assert)
12+
require('./merge.test').default(test, assert)
1213

1314
process.on('unhandledRejection', e => {
1415
console.error(e)

‎test/index.test.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,25 @@ export default (test, assert) => {
267267
await gretch(`http://127.0.0.1:${port}`, {
268268
timeout: 50000,
269269
hooks: {
270-
before (request) {
270+
before (request, opts) {
271+
assert(request.url)
272+
assert(opts.timeout)
271273
hooks++
272274
},
273-
after ({ status }) {
274-
hooks++
275-
}
275+
after: [
276+
(response, opts) => {
277+
assert(response.status)
278+
assert(opts.timeout)
279+
hooks++
280+
},
281+
() => {
282+
hooks++
283+
}
284+
]
276285
}
277286
}).json()
278287

279-
assert(hooks === 2)
288+
assert(hooks === 3)
280289

281290
server.close()
282291

‎test/merge.test.ts

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { merge } from '../lib/merge'
2+
3+
export default (test, assert) => {
4+
test('merges primitives', () => {
5+
const o = merge(
6+
{
7+
str: 'in',
8+
bool: false,
9+
int: 0,
10+
arr: ['in'],
11+
obj: {
12+
prop: 'in'
13+
}
14+
},
15+
{
16+
str: 'out',
17+
bool: true,
18+
int: 1,
19+
arr: ['out'],
20+
obj: {
21+
prop: 'out'
22+
}
23+
}
24+
)
25+
26+
assert.equal(o.str, 'out')
27+
assert.equal(o.bool, true)
28+
assert.equal(o.int, 1)
29+
assert.deepEqual(o.arr, ['in', 'out'])
30+
assert.equal(o.obj.prop, 'out')
31+
})
32+
33+
test('merges headers', () => {
34+
const o = merge(
35+
{
36+
headers: new Headers({
37+
'X-In': 'in',
38+
'X-Header': 'in'
39+
})
40+
},
41+
{
42+
headers: {
43+
'X-Out': 'out',
44+
'X-Header': 'out'
45+
}
46+
}
47+
)
48+
49+
assert.equal(o.headers['x-header'], 'out')
50+
assert.equal(o.headers['x-in'], 'in')
51+
assert.equal(o.headers['x-out'], 'out')
52+
})
53+
54+
test('overwrites mixed values', () => {
55+
const o = merge(
56+
{
57+
timeout: 100,
58+
retry: false,
59+
hooks: {
60+
after () {}
61+
}
62+
},
63+
{
64+
timeout: 200,
65+
retry: {
66+
attempts: 3
67+
},
68+
hooks: {
69+
after: [() => {}]
70+
}
71+
}
72+
)
73+
74+
assert.equal(o.timeout, 200)
75+
// @ts-ignore
76+
assert.equal(o.retry.attempts, 3)
77+
assert(Array.isArray(o.hooks.after))
78+
})
79+
80+
test('merges hooks', () => {
81+
const o = merge(
82+
{
83+
hooks: {
84+
before () {}
85+
}
86+
},
87+
{
88+
hooks: {
89+
after () {}
90+
}
91+
}
92+
)
93+
94+
assert(typeof o.hooks.before === 'function')
95+
assert(typeof o.hooks.after === 'function')
96+
})
97+
98+
test('clones reference object', () => {
99+
const defaults = {
100+
prop: 'default'
101+
}
102+
103+
const o = merge(defaults, {
104+
prop: 'out'
105+
})
106+
107+
assert.equal(defaults.prop, 'default')
108+
assert.equal(o.prop, 'out')
109+
})
110+
}

0 commit comments

Comments
 (0)
Please sign in to comment.