Skip to content

Commit 32c40bf

Browse files
committed
fix(kitsu-core): serialise v9 relationship structures
This was intended to be part of the 9.0.0 release Unfortuantely I missed it hadn't been done and shipped incompatible deserialise and serialise functions BREAKING CHANGE
1 parent 31b21da commit 32c40bf

2 files changed

Lines changed: 153 additions & 69 deletions

File tree

packages/kitsu-core/src/serialise/index.js

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,21 @@ function isValid (isArray, type, payload, method) {
4343
* @returns {Object} Serialised relationship
4444
* @private
4545
*/
46-
function serialiseRelationOne (node, relations, key) {
47-
relations[key] = { data: {} }
48-
for (const prop of Object.keys(node[key].data)) {
49-
const propNode = node[key].data[prop]
50-
let propRelations = relations[key].data
51-
// Pull everything but id and type into data.attributes
52-
if (prop && ![ 'id', 'type' ].includes(prop)) {
53-
propRelations = serialiseAttr(propNode, prop, propRelations)
54-
} else propRelations[prop] = propNode
46+
function serialiseRelationOne (node, nodeType) {
47+
let relation = {}
48+
for (const prop of Object.keys(node)) {
49+
if (prop &&
50+
// Properties not being put in data.attributes
51+
![ 'id', 'type' ].includes(prop) &&
52+
// Exclude a valid JSON:API links object being put in data.attributes
53+
!(prop === 'links' && (node[prop].self || node[prop].related))
54+
) {
55+
relation = serialiseAttr(node[prop], prop, relation)
56+
} else relation[prop] = node[prop]
5557
}
56-
return relations
58+
// Guess relationship type if not provided
59+
if (!relation.type) relation.type = nodeType
60+
return relation
5761
}
5862

5963
/**
@@ -65,50 +69,35 @@ function serialiseRelationOne (node, relations, key) {
6569
* @returns {Object} Serialised relationship
6670
* @private
6771
*/
68-
function serialiseRelationMany (node, relations, key) {
69-
relations[key] = { data: [] }
70-
for (const prop of node[key].data) {
71-
relations[key].data.push(prop)
72+
function serialiseRelationMany (node, nodeType) {
73+
const relation = []
74+
for (const prop of node) {
75+
const serialised = serialiseRelationOne(prop)
76+
// Guess relationship type if not provided
77+
if (!serialised.type) serialised.type = nodeType
78+
relation.push(serialised)
7279
}
73-
return relations
80+
return relation
7481
}
7582

7683
/**
7784
* Serialises a relational object to JSON:API format
7885
*
79-
* @param {Object} node Relation object
80-
* @returns {Object} Serialised relationship
81-
* @private
82-
*/
83-
function serialiseRelation (node) {
84-
// Create a new object to handle collisions with attributes.attributes
85-
let relations = {}
86-
for (const key in node) {
87-
const isToMany = Array.isArray(node[key].data)
88-
relations = isToMany
89-
? serialiseRelationMany(node, relations, key)
90-
: serialiseRelationOne(node, relations, key)
91-
}
92-
return relations
93-
}
94-
95-
/**
96-
* Serialises an object to JSON:API format
97-
*
9886
* @param {Object} node Resource object
9987
* @param {string} nodeType Resource type of the object
10088
* @param {string} key The resource object's key value
10189
* @param {Object} data Root JSON:API data object
10290
* @private
10391
*/
104-
function serialiseObject (node, nodeType, key, data) {
92+
function serialiseRelation (node, nodeType, key, data) {
10593
if (!data.relationships) data.relationships = {}
106-
// Guess type if not provided
107-
if (!node.type) node.type = nodeType
10894
data.relationships[key] = {
109-
data: Object.assign(node)
95+
data: Array.isArray(node.data)
96+
? serialiseRelationMany(node.data, nodeType)
97+
: serialiseRelationOne(node.data, nodeType)
11098
}
111-
data.relationships = serialiseRelation(data.relationships)
99+
if (node?.links?.self || node?.links?.related) data.relationships[key].links = node.links
100+
if (node?.meta) data.relationships[key].meta = node.meta
112101
return data
113102
}
114103

@@ -123,8 +112,9 @@ function serialiseObject (node, nodeType, key, data) {
123112
*/
124113
function serialiseArray (node, nodeType, key, data) {
125114
if (!data.relationships) data.relationships = {}
115+
126116
data.relationships[key] = {
127-
data: node.map(({ id, type, ...attributes }) => {
117+
data: node.data.map(({ id, type, ...attributes }) => {
128118
return {
129119
id,
130120
type: type || nodeType,
@@ -146,7 +136,8 @@ function serialiseArray (node, nodeType, key, data) {
146136
*/
147137
function serialiseAttr (node, key, data) {
148138
if (!data.attributes) data.attributes = {}
149-
data.attributes[key] = node
139+
if (key === 'links' && (node.self || node.related)) data.links = node
140+
else data.attributes[key] = node
150141
return data
151142
}
152143

@@ -158,7 +149,10 @@ function serialiseAttr (node, key, data) {
158149
* @private
159150
*/
160151
function hasID (node) {
161-
return Object.prototype.hasOwnProperty.call(node, 'id')
152+
if (!node.data) return false
153+
// Check if relationship is to-many
154+
const nodeData = Array.isArray(node.data) ? node.data[0] : node.data
155+
return Object.prototype.hasOwnProperty.call(nodeData, 'id')
162156
}
163157

164158
/**
@@ -198,16 +192,17 @@ function serialiseRootObject (type, payload, method, options) {
198192
isValid(false, type, payload, method)
199193
type = options.pluralTypes(options.camelCaseTypes(type))
200194
let data = { type }
195+
// ID not required for POST requests
201196
if (payload?.id) data.id = String(payload.id)
202197
for (const key in payload) {
203198
const node = payload[key]
204199
const nodeType = options.pluralTypes(options.camelCaseTypes(key))
205200
// 1. Skip null nodes, 2. Only grab objects, 3. Filter to only serialise relationable objects
206201
if (node !== null && node?.constructor === Object && hasID(node)) {
207-
data = serialiseObject(node, nodeType, key, data)
202+
data = serialiseRelation(node, nodeType, key, data)
208203
// 1. Skip null nodes, 2. Only grab arrays, 3. Filter to only serialise relationable arrays
209-
} else if (node !== null && Array.isArray(node) && (node.length > 0 && hasID(node[0]))) {
210-
data = serialiseArray(node, nodeType, key, data)
204+
// } else if (node !== null && node?.constructor === Object && hasID(node)) {
205+
// data = serialiseArray(node, nodeType, key, data)
211206
// 1. Don't place id/key inside attributes object
212207
} else if (key !== 'id' && key !== 'type') {
213208
data = serialiseAttr(node, key, data)

packages/kitsu-core/src/serialise/index.spec.js

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ describe('kitsu-core', () => {
8181
'libraryEntries',
8282
{
8383
user: {
84-
id: '2'
84+
data: {
85+
id: '2'
86+
}
8587
}
8688
},
8789
undefined,
@@ -110,16 +112,18 @@ describe('kitsu-core', () => {
110112
const input = serialise(
111113
'libraryEntries',
112114
{
113-
user: [
114-
{
115-
id: '2',
116-
type: 'users',
117-
content: 'yuzu'
118-
},
119-
{
120-
id: '3'
121-
}
122-
]
115+
user: {
116+
data: [
117+
{
118+
id: '2',
119+
type: 'users',
120+
content: 'yuzu'
121+
},
122+
{
123+
id: '3'
124+
}
125+
]
126+
}
123127
},
124128
undefined,
125129
{
@@ -305,10 +309,12 @@ describe('kitsu-core', () => {
305309
expect.assertions(1)
306310
const input = serialise('resourceModel', {
307311
myRelationship: {
308-
id: '1',
309-
type: 'relationshipModel',
310-
content: 'Hello',
311-
attributes: 'Keep me'
312+
data: {
313+
id: '1',
314+
type: 'relationshipModel',
315+
content: 'Hello',
316+
attributes: 'Keep me'
317+
}
312318
}
313319
})
314320
expect(input).toEqual({
@@ -334,9 +340,11 @@ describe('kitsu-core', () => {
334340
expect.assertions(1)
335341
const input = serialise('resourceModel', [ {
336342
myRelationship: {
337-
id: '1',
338-
type: 'relationshipModel',
339-
content: 'Hello'
343+
data: {
344+
id: '1',
345+
type: 'relationshipModel',
346+
content: 'Hello'
347+
}
340348
}
341349
} ])
342350
expect(input).toEqual({
@@ -381,12 +389,16 @@ describe('kitsu-core', () => {
381389
it('serialises type arrays into relationships', () => {
382390
expect.assertions(1)
383391
const input = serialise('resourceModels', {
384-
arrayRelation: [ {
385-
id: '1',
386-
type: 'arrayRelations',
387-
content: 'Hey',
388-
attributes: 'Keep me'
389-
} ]
392+
arrayRelation: {
393+
data: [
394+
{
395+
id: '1',
396+
type: 'arrayRelations',
397+
content: 'Hey',
398+
attributes: 'Keep me'
399+
}
400+
]
401+
}
390402
})
391403
expect(input).toEqual({
392404
data: {
@@ -450,5 +462,82 @@ describe('kitsu-core', () => {
450462
const input = serialise('posts', resource)
451463
expect(input).toEqual({ data: resourceOutput })
452464
})
465+
466+
it('uses the new system', () => {
467+
expect.assertions(1)
468+
const input = {
469+
id: '1',
470+
type: 'libraryEntries',
471+
links: { self: 'library-entries/1' },
472+
ratingTwenty: 10,
473+
user: {
474+
links: {
475+
self: 'library-entries/1/relationships/user',
476+
related: 'library-entries/1/user'
477+
},
478+
meta: { some: 'meta info' },
479+
data: {
480+
id: '2',
481+
type: 'users',
482+
name: 'Example',
483+
links: { self: 'users/2' }
484+
}
485+
},
486+
unit: {
487+
links: {
488+
self: 'library-entries/1/relationships/unit',
489+
related: 'library-entries/1/unit'
490+
},
491+
meta: { extra: 'info' },
492+
data: [
493+
{
494+
id: '3',
495+
type: 'episodes',
496+
number: 12,
497+
links: { self: 'episodes/3' }
498+
}
499+
]
500+
}
501+
}
502+
const output = {
503+
data: {
504+
id: '1',
505+
type: 'libraryEntries',
506+
links: { self: 'library-entries/1' },
507+
attributes: { ratingTwenty: 10 },
508+
relationships: {
509+
user: {
510+
links: {
511+
self: 'library-entries/1/relationships/user',
512+
related: 'library-entries/1/user'
513+
},
514+
meta: { some: 'meta info' },
515+
data: {
516+
id: '2',
517+
type: 'users',
518+
attributes: { name: 'Example' },
519+
links: { self: 'users/2' }
520+
}
521+
},
522+
unit: {
523+
links: {
524+
self: 'library-entries/1/relationships/unit',
525+
related: 'library-entries/1/unit'
526+
},
527+
meta: { extra: 'info' },
528+
data: [
529+
{
530+
id: '3',
531+
type: 'episodes',
532+
attributes: { number: 12 },
533+
links: { self: 'episodes/3' }
534+
}
535+
]
536+
}
537+
}
538+
}
539+
}
540+
expect(serialise('libraryEntries', input)).toStrictEqual(output)
541+
})
453542
})
454543
})

0 commit comments

Comments
 (0)