Skip to content

Commit 0266f66

Browse files
committed
feat(kitsu): implement opt-in data hoisting on returned responses
1 parent 72dc2be commit 0266f66

10 files changed

Lines changed: 76 additions & 37 deletions

File tree

packages/kitsu/README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ All code released under [MIT]
236236

237237
### Kitsu
238238

239-
[packages/kitsu/src/index.js:31-567](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L31-L567 "Source code on GitHub")
239+
[packages/kitsu/src/index.js:39-575](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L39-L575 "Source code on GitHub")
240240

241241
Creates a new `kitsu` instance
242242

@@ -252,6 +252,11 @@ Creates a new `kitsu` instance
252252
* `options.pluralize` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If enabled, `/user` will become `/users` in the URL request and `type` will be pluralized in POST, PATCH and DELETE requests (optional, default `true`)
253253
* `options.timeout` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Set the request timeout in milliseconds (optional, default `30000`)
254254
* `options.axiosOptions` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Additional options for the axios instance (see [axios/axios#request-config](https://github.com/axios/axios#request-config) for details)
255+
* `options.hoistData` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If enabled, the contents of the `data` property will be hoisted to the parent. This provides a flatter response object, but removes access to `links` and `meta` properties. It will transform:```js
256+
{ data: { id: '1', type: 'people', coworkers: data: [ { id: '2', type: 'people' } ] } }
257+
```into the following:```js
258+
{ id: '1', type: 'people', coworkers: [ { id: '2', type: 'people' } ] }
259+
``` (optional, default `false`)
255260

256261
#### Examples
257262

@@ -282,7 +287,7 @@ const api = new Kitsu({
282287

283288
#### plural
284289

285-
[packages/kitsu/src/index.js:58-59](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L58-L59 "Source code on GitHub")
290+
[packages/kitsu/src/index.js:66-67](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L66-L67 "Source code on GitHub")
286291

287292
* **See**: <https://www.npmjs.com/package/pluralize> for documentation
288293
* **See**: [Kitsu](#kitsu) constructor options for disabling pluralization
@@ -301,7 +306,7 @@ api.plural.plural('paper') //=> 'paper'
301306

302307
#### headers
303308

304-
[packages/kitsu/src/index.js:73-73](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L73-L73 "Source code on GitHub")
309+
[packages/kitsu/src/index.js:81-81](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L81-L81 "Source code on GitHub")
305310

306311
Get the current headers or add additional headers
307312

@@ -329,7 +334,7 @@ Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/G
329334

330335
#### interceptors
331336

332-
[packages/kitsu/src/index.js:120-120](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L120-L120 "Source code on GitHub")
337+
[packages/kitsu/src/index.js:128-128](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L128-L128 "Source code on GitHub")
333338

334339
* **See**: <https://github.com/axios/axios#interceptors> for documentation
335340

@@ -376,7 +381,7 @@ api.interceptors.request.eject(myInterceptor)
376381

377382
#### get
378383

379-
[packages/kitsu/src/index.js:218-253](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L218-L253 "Source code on GitHub")
384+
[packages/kitsu/src/index.js:226-261](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L226-L261 "Source code on GitHub")
380385

381386
Fetch resources (alias `fetch`)
382387

@@ -513,7 +518,7 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
513518

514519
#### patch
515520

516-
[packages/kitsu/src/index.js:289-323](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L289-L323 "Source code on GitHub")
521+
[packages/kitsu/src/index.js:297-331](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L297-L331 "Source code on GitHub")
517522

518523
Update a resource (alias `update`)
519524

@@ -575,7 +580,7 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
575580

576581
#### post
577582

578-
[packages/kitsu/src/index.js:358-390](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L358-L390 "Source code on GitHub")
583+
[packages/kitsu/src/index.js:366-398](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L366-L398 "Source code on GitHub")
579584

580585
Create a new resource (alias `create`)
581586

@@ -624,7 +629,7 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
624629

625630
#### delete
626631

627-
[packages/kitsu/src/index.js:410-450](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L410-L450 "Source code on GitHub")
632+
[packages/kitsu/src/index.js:418-458](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L418-L458 "Source code on GitHub")
628633

629634
Remove a resource (alias `remove`)
630635

@@ -662,7 +667,7 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
662667

663668
#### self
664669

665-
[packages/kitsu/src/index.js:474-483](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L474-L483 "Source code on GitHub")
670+
[packages/kitsu/src/index.js:482-491](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L482-L491 "Source code on GitHub")
666671

667672
Get the authenticated user's data
668673

@@ -700,7 +705,7 @@ Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/
700705

701706
#### request
702707

703-
[packages/kitsu/src/index.js:538-566](https://github.com/wopian/kitsu/blob/a794fdd4c0266be8d8404455ebf2847e47e66a64/packages/kitsu/src/index.js#L538-L566 "Source code on GitHub")
708+
[packages/kitsu/src/index.js:546-574](https://github.com/wopian/kitsu/blob/9039f9be49ca8ba032ad6b7099316b6751e18198/packages/kitsu/src/index.js#L546-L574 "Source code on GitHub")
704709

705710
Send arbitrary requests
706711

packages/kitsu/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"main": "dist/index",
1616
"module": "dist/index",
1717
"types": "types/index.d.ts",
18+
"type": "module",
1819
"homepage": "https://github.com/wopian/kitsu/tree/master/packages/kitsu#readme",
1920
"repository": "https://github.com/wopian/kitsu",
2021
"bugs": {

packages/kitsu/src/delete.spec.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('kitsu', () => {
1313
it('uses provided axios options', async () => {
1414
expect.assertions(1)
1515
const api = new Kitsu()
16+
// @ts-ignore - testing axios options passthrough
1617
api.axios = { delete: jest.fn().mockReturnValue({ data: '' }) }
1718
await api.delete('anime', 1, { axiosOptions: { withCredentials: true } })
1819
expect(api.axios.delete).toHaveBeenCalledWith('anime/1', expect.objectContaining({ withCredentials: true }))
@@ -52,7 +53,7 @@ describe('kitsu', () => {
5253
})
5354
return [ 200 ]
5455
})
55-
await expect(await api.delete('post', 1)).toEqual({ status: 200 })
56+
expect(await api.delete('post', 1)).toEqual({ status: 200 })
5657
})
5758

5859
it('handles nested routes', async () => {
@@ -67,7 +68,7 @@ describe('kitsu', () => {
6768
})
6869
return [ 200 ]
6970
})
70-
await expect(await api.delete('posts/1/comments', 1)).toEqual({ status: 200 })
71+
expect(await api.delete('posts/1/comments', 1)).toEqual({ status: 200 })
7172
})
7273

7374
it('deletes multiple resources (bulk extension)', async () => {
@@ -82,13 +83,14 @@ describe('kitsu', () => {
8283
})
8384
return [ 200 ]
8485
})
85-
await expect(await api.delete('post', [ 1, 2 ])).toEqual({ status: 200 })
86+
expect(await api.delete('post', [ 1, 2 ])).toEqual({ status: 200 })
8687
})
8788

8889
it('throws an error if ID is missing', async () => {
8990
expect.assertions(1)
9091
const api = new Kitsu()
9192
try {
93+
// @ts-ignore - testing error throw
9294
await api.delete('posts')
9395
} catch (err) {
9496
expect(err.message).toEqual('DELETE requires an ID for the posts type')

packages/kitsu/src/get.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
getSingle,
1010
getSingleWithIncludes,
1111
getSingleWithNestedIncludes
12-
} from 'specification'
12+
} from '../../../specification'
1313

1414
const mock = new MockAdapter(axios)
1515

@@ -22,6 +22,7 @@ describe('kitsu', () => {
2222
it('uses provided axios options', async () => {
2323
expect.assertions(1)
2424
const api = new Kitsu()
25+
// @ts-ignore - testing axios options passthrough
2526
api.axios = { get: jest.fn().mockReturnValue({ data: '' }) }
2627
await api.get('anime', { axiosOptions: { withCredentials: true } })
2728
expect(api.axios.get).toHaveBeenCalledWith('anime', expect.objectContaining({ withCredentials: true }))
@@ -44,7 +45,7 @@ describe('kitsu', () => {
4445
} ]
4546
})
4647
const response = await api.get('anime', { headers: { extra: true } })
47-
await expect(await response).toMatchObject({
48+
expect(await response).toMatchObject({
4849
data: [],
4950
headers: {
5051
Accept: 'application/vnd.api+json',

packages/kitsu/src/index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import pluralise from 'pluralize'
1414
* @param {boolean} [options.pluralize=true] If enabled, `/user` will become `/users` in the URL request and `type` will be pluralized in POST, PATCH and DELETE requests
1515
* @param {number} [options.timeout=30000] Set the request timeout in milliseconds
1616
* @param {Object} [options.axiosOptions] Additional options for the axios instance (see [axios/axios#request-config](https://github.com/axios/axios#request-config) for details)
17+
* @param {boolean} [options.hoistData=false] If enabled, the contents of the `data` property will be hoisted to the parent. This provides a flatter response object, but removes access to `links` and `meta` properties. It will transform:
18+
* ```js
19+
* { data: { id: '1', type: 'people', coworkers: data: [ { id: '2', type: 'people' } ] } }
20+
* ```
21+
* into the following:
22+
* ```js
23+
* { id: '1', type: 'people', coworkers: [ { id: '2', type: 'people' } ] }
24+
* ```
1725
* @example <caption>Using with kitsu.app's API</caption>
1826
* const api = new Kitsu()
1927
* @example <caption>Using another API server</caption>
@@ -81,6 +89,8 @@ export default class Kitsu {
8189
...options.axiosOptions
8290
})
8391

92+
this.hoistData = options.hoistData || false
93+
8494
this.fetch = this.get
8595
this.update = this.patch
8696
this.create = this.post
@@ -240,7 +250,7 @@ export default class Kitsu {
240250
})
241251

242252
return {
243-
...deserialise(data),
253+
...deserialise(data, { hoistData: this.hoistData }),
244254
status,
245255
...(responseHeaders && Object.keys(responseHeaders).length
246256
? { headers: responseHeaders }
@@ -310,7 +320,7 @@ export default class Kitsu {
310320
)
311321

312322
return {
313-
...deserialise(data),
323+
...deserialise(data, { hoistData: this.hoistData }),
314324
status,
315325
...(responseHeaders && Object.keys(responseHeaders).length
316326
? { headers: responseHeaders }
@@ -377,7 +387,7 @@ export default class Kitsu {
377387
)
378388

379389
return {
380-
...deserialise(data),
390+
...deserialise(data, { hoistData: this.hoistData }),
381391
status,
382392
...(responseHeaders && Object.keys(responseHeaders).length
383393
? { headers: responseHeaders }
@@ -437,7 +447,7 @@ export default class Kitsu {
437447
})
438448

439449
return {
440-
...deserialise(data),
450+
...deserialise(data, { hoistData: this.hoistData }),
441451
status,
442452
...(responseHeaders && Object.keys(responseHeaders).length
443453
? { headers: responseHeaders }
@@ -553,7 +563,7 @@ export default class Kitsu {
553563
})
554564

555565
return {
556-
...deserialise(data),
566+
...deserialise(data, { hoistData: this.hoistData }),
557567
status,
558568
...(responseHeaders && Object.keys(responseHeaders).length
559569
? { headers: responseHeaders }

packages/kitsu/src/patch.spec.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Kitsu from 'kitsu'
44
import {
55
patchSingle,
66
patchSingleMissingID
7-
} from 'specification'
7+
} from '../../../specification'
88

99
const mock = new MockAdapter(axios)
1010

@@ -17,6 +17,7 @@ describe('kitsu', () => {
1717
it('uses provided axios options', async () => {
1818
expect.assertions(1)
1919
const api = new Kitsu()
20+
// @ts-ignore - testing axios options passthrough
2021
api.axios = { patch: jest.fn().mockReturnValue({ data: '' }) }
2122
await api.patch('anime', { id: '1', type: 'anime' }, { axiosOptions: { withCredentials: true } })
2223
expect(api.axios.patch).toHaveBeenCalledWith('anime/1', { data: { id: '1', type: 'anime' } }, expect.objectContaining({ withCredentials: true }))
@@ -36,7 +37,7 @@ describe('kitsu', () => {
3637
Accept: 'application/vnd.api+json'
3738
} ]
3839
})
39-
await expect(await api.patch('anime', { id: '1', type: 'anime' }, { headers: { extra: true } })).toMatchObject({
40+
expect(await api.patch('anime', { id: '1', type: 'anime' }, { headers: { extra: true } })).toMatchObject({
4041
headers: {
4142
Accept: 'application/vnd.api+json'
4243
},
@@ -59,7 +60,7 @@ describe('kitsu', () => {
5960
})
6061
return [ 200 ]
6162
})
62-
await expect(await api.patch('post', { id: '1', content: 'Hello World' })).toEqual({ status: 200 })
63+
expect(await api.patch('post', { id: '1', content: 'Hello World' })).toEqual({ status: 200 })
6364
})
6465

6566
it('sends bulk data in request', async () => {
@@ -86,7 +87,7 @@ describe('kitsu', () => {
8687
})
8788
return [ 200 ]
8889
})
89-
await expect(await api.patch('post', [
90+
expect(await api.patch('post', [
9091
{ id: '1', content: 'Hello World' },
9192
{ id: '2', content: 'Hey World' }
9293
])).toEqual({ status: 200 })
@@ -96,6 +97,7 @@ describe('kitsu', () => {
9697
expect.assertions(1)
9798
const api = new Kitsu()
9899
try {
100+
// @ts-ignore - testing invalid input
99101
await api.patch('posts')
100102
} catch (err) {
101103
expect(err.message).toEqual('PATCH requires an object or array body')

packages/kitsu/src/post.spec.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('kitsu', () => {
1313
it('uses provided axios options', async () => {
1414
expect.assertions(1)
1515
const api = new Kitsu()
16+
// @ts-ignore - testing axios options passthrough
1617
api.axios = { post: jest.fn().mockReturnValue({ data: '' }) }
1718
await api.post('anime', { id: '1', type: 'anime' }, { axiosOptions: { withCredentials: true } })
1819
expect(api.axios.post).toHaveBeenCalledWith('anime', { data: { id: '1', type: 'anime' } }, expect.objectContaining({ withCredentials: true }))
@@ -32,7 +33,7 @@ describe('kitsu', () => {
3233
Accept: 'application/vnd.api+json'
3334
} ]
3435
})
35-
await expect(await api.post('anime', { id: '1', type: 'anime' }, { headers: { extra: true } })).toMatchObject({
36+
expect(await api.post('anime', { id: '1', type: 'anime' }, { headers: { extra: true } })).toMatchObject({
3637
headers: {
3738
Accept: 'application/vnd.api+json'
3839
},
@@ -54,7 +55,7 @@ describe('kitsu', () => {
5455
})
5556
return [ 200 ]
5657
})
57-
await expect(await api.post('anime', { type: 'anime', name: 'Name' })).toEqual({ status: 200 })
58+
expect(await api.post('anime', { type: 'anime', name: 'Name' })).toEqual({ status: 200 })
5859
})
5960

6061
it('handles nested routes', async () => {
@@ -71,7 +72,7 @@ describe('kitsu', () => {
7172
})
7273
return [ 200 ]
7374
})
74-
await expect(await api.post('something/1/relationships/anime', { type: 'anime', name: 'Name' })).toEqual({ status: 200 })
75+
expect(await api.post('something/1/relationships/anime', { type: 'anime', name: 'Name' })).toEqual({ status: 200 })
7576
})
7677

7778
it('sends data in request with client-generated ID', async () => {
@@ -89,7 +90,7 @@ describe('kitsu', () => {
8990
})
9091
return [ 200 ]
9192
})
92-
await expect(await api.post('anime', { id: 123456789, type: 'anime', name: 'Name' })).toEqual({ status: 200 })
93+
expect(await api.post('anime', { id: 123456789, type: 'anime', name: 'Name' })).toEqual({ status: 200 })
9394
})
9495

9596
it('throws an error if missing a valid JSON object body', async () => {
@@ -117,8 +118,9 @@ describe('kitsu', () => {
117118
})
118119
return [ 200 ]
119120
})
120-
await expect(await api.post('anime')).toEqual({ status: 200 })
121-
await expect(await api.post('anime', {})).toEqual({ status: 200 })
121+
// @ts-ignore - testing invalid input
122+
expect(await api.post('anime')).toEqual({ status: 200 })
123+
expect(await api.post('anime', {})).toEqual({ status: 200 })
122124
})
123125

124126
it('sends data in request if given empty JSON object in array body', async () => {
@@ -130,7 +132,7 @@ describe('kitsu', () => {
130132
})
131133
return [ 200 ]
132134
})
133-
await expect(await api.post('anime', [ {} ])).toEqual({ status: 200 })
135+
expect(await api.post('anime', [ {} ])).toEqual({ status: 200 })
134136
})
135137
})
136138
})

0 commit comments

Comments
 (0)