kitsu-core
Version:
Simple, lightweight & framework agnostic JSON:API (de)serialsation components
124 lines (120 loc) • 4.04 kB
JavaScript
import { deattribute } from './deattribute.mjs';
import { filterIncludes } from './filterIncludes.mjs';
import './error.mjs';
const isDeepEqual = (left, right) => {
if (!left || !right) {
return left === right;
}
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
if (leftKeys.length !== rightKeys.length) return false;
for (const key of leftKeys) {
const leftValue = left[key];
const rightValue = right[key];
const isObjects = isObject(leftValue) && isObject(rightValue);
if (isObjects && !isDeepEqual(leftValue, rightValue) || !isObjects && leftValue !== rightValue) {
return false;
}
}
return true;
};
const isObject = object => {
return object != null && typeof object === 'object';
};
function link({
id,
type
}, included, previouslyLinked, relationshipCache) {
const filtered = filterIncludes(included, {
id,
type
});
previouslyLinked[`${type}#${id}`] = filtered;
if (filtered.relationships) {
linkRelationships(filtered, included, previouslyLinked, relationshipCache);
}
return deattribute(filtered);
}
function linkArray(data, included, key, previouslyLinked, relationshipCache) {
data[key] = {};
if (data.relationships[key].links) data[key].links = data.relationships[key].links;
if (data.relationships[key].meta) data[key].meta = data.relationships[key].meta;
data[key].data = [];
for (const resource of data.relationships[key].data) {
const cache = previouslyLinked[`${resource.type}#${resource.id}`];
let relationship = cache || link(resource, included, previouslyLinked, relationshipCache);
if (resource.meta || relationship.meta) {
relationship = {
...relationship,
meta: {
...relationship.meta,
...resource.meta
}
};
}
data[key].data.push(relationship);
}
delete data.relationships[key];
}
function linkObject(data, included, key, previouslyLinked, relationshipCache) {
data[key] = {};
const resource = data.relationships[key].data;
const cache = previouslyLinked[`${resource.type}#${resource.id}`];
if (cache) {
let resourceCache = null;
if (!isDeepEqual(cache.meta, resource.meta)) {
resourceCache = {
...cache,
meta: {
...cache.meta,
...resource.meta
}
};
} else {
resourceCache = cache;
}
data[key].data = resourceCache;
} else {
data[key].data = link(resource, included, previouslyLinked, relationshipCache);
}
if (resource.meta || data[key].data.meta) {
data[key].data = {
...data[key].data,
meta: {
...data[key].data.meta,
...resource.meta
}
};
}
const cacheKey = `${data.type}#${data.id}#${key}`;
const relationships = relationshipCache[cacheKey] || data.relationships[key];
if (!relationshipCache[cacheKey]) relationshipCache[cacheKey] = relationships;
if (relationships?.links) data[key].links = relationships.links;
if (relationships?.meta) data[key].meta = relationships.meta;
delete data.relationships[key];
}
function linkAttr(data, key) {
data[key] = {};
if (data.relationships[key].links) data[key].links = data.relationships[key].links;
if (data.relationships[key].meta) data[key].meta = data.relationships[key].meta;
delete data.relationships[key];
}
function linkRelationships(data, included = [], previouslyLinked = {}, relationshipCache = {}) {
const {
relationships
} = data;
for (const key in relationships) {
if (Array.isArray(relationships[key]?.data)) {
linkArray(data, included, key, previouslyLinked, relationshipCache);
} else if (relationships[key].data) {
linkObject(data, included, key, previouslyLinked, relationshipCache);
} else {
linkAttr(data, key);
}
}
if (Object.keys(relationships || []).length === 0 && typeof relationships === 'object' && !Array.isArray(relationships) && relationships !== null) {
delete data.relationships;
}
return data;
}
export { linkRelationships };