ts-japi
Version:
A highly-modular (typescript-friendly)-framework agnostic library for serializing data to the JSON:API specification
187 lines • 7.64 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Helpers = void 0;
exports.recurseRelators = recurseRelators;
exports.normalizeRelators = normalizeRelators;
const relator_1 = __importDefault(require("../classes/relator"));
async function recurseRelatorsDepth(data, relators, depth, keys, relatorDataCache) {
const included = [];
let curRelatorDataCache = relatorDataCache || new Map();
// Required to support backwards compatability where the first dataCache may
// not be passed in. All subsequent iterations will contain a dataCache
if (!relatorDataCache && depth > 0) {
for (const name of Object.keys(relators)) {
const cache = curRelatorDataCache.get(relators[name]) || [];
curRelatorDataCache.set(relators[name], cache);
for (const datum of data) {
const relatedData = await relators[name].getRelatedData(datum);
if (relatedData !== null) {
cache.push(...(Array.isArray(relatedData) ? relatedData : [relatedData]));
}
}
}
}
let remainingDepth = depth;
while (remainingDepth-- > 0 && curRelatorDataCache.size > 0) {
const newRelatorDataCache = new Map();
for (const [relator, cache] of curRelatorDataCache) {
for (const item of cache) {
const resource = await relator.getRelatedResource(item, undefined, undefined, newRelatorDataCache);
const key = resource.getKey();
if (!keys.includes(key)) {
keys.push(key);
included.push(resource);
}
}
}
curRelatorDataCache = newRelatorDataCache;
}
return included;
}
async function recurseRelators(data, relators, include, keys, relatorDataCache) {
if (include === undefined || typeof include === "number") {
return recurseRelatorsDepth(data, relators, include ?? 0, keys, relatorDataCache);
}
const included = [];
let curRelatorDataCache = relatorDataCache || new Map();
// Required to support backwards compatability where the first dataCache may
// not be passed in. All subsequent iterations will contain a dataCache
if (!relatorDataCache && include.length > 0) {
for (const name of Object.keys(relators)) {
const cache = curRelatorDataCache.get(relators[name]) || [];
curRelatorDataCache.set(relators[name], cache);
for (const datum of data) {
const relatedData = await relators[name].getRelatedData(datum);
if (relatedData !== null) {
cache.push(...(Array.isArray(relatedData) ? relatedData : [relatedData]));
}
}
}
}
const maxDepth = Math.max(...include.map((i) => i.split(".").length));
let currentDepth = 0;
while (currentDepth < maxDepth) {
const newRelatorDataCache = new Map();
const includeFields = include
.map((i) => i.split("."))
.filter((i) => i[currentDepth])
.map((i) => ({
field: i[currentDepth],
hasMore: i.length > currentDepth + 1,
}))
.reduce((acc, i) => {
const match = acc.find((j) => j.field === i.field);
if (match) {
match.hasMore = match.hasMore || i.hasMore;
}
else {
acc.push(i);
}
return acc;
}, []);
for (const [relator, cache] of curRelatorDataCache) {
const shouldBuildRelatedCache = (!includeFields ||
includeFields
?.filter((i) => i.field === relator.relatedName)
?.some((i) => i.hasMore)) ??
false;
for (const cacheItem of cache) {
// Include if,
// - includeFields !== undefined
// - includeFields has entry where field = relatedName
if (!includeFields ||
includeFields.map((i) => i.field).includes(relator.relatedName)) {
const key = `${relator.serializer.collectionName}:${cacheItem[relator.serializer.getIdKeyFieldName()]}`;
if (!keys.includes(key)) {
// const key = resource.getKey();
const resource = await relator.getRelatedResource(cacheItem, undefined, undefined,
// Only build the cache for the next iteration if needed.
shouldBuildRelatedCache ? newRelatorDataCache : undefined);
keys.push(key);
included.push(resource);
}
}
}
}
currentDepth++;
curRelatorDataCache = newRelatorDataCache;
}
return included;
}
function normalizeRelators(relators) {
const normalizedRelators = {};
if (relators) {
if (relators instanceof relator_1.default) {
normalizedRelators[relators.relatedName] = relators;
return normalizedRelators;
}
if (Array.isArray(relators)) {
for (const relator of relators) {
normalizedRelators[relator.relatedName] = relator;
}
return normalizedRelators;
}
return relators;
}
return undefined;
}
class Helpers {
projectAttributes;
relators;
constructor(options) {
// Relators
this.relators = normalizeRelators(options.relators);
// Projection
if (options.projection === undefined) {
this.projectAttributes = () => undefined;
}
else if (options.projection === null) {
const relatorKeys = this.relators
? new Set(Object.keys(this.relators))
: undefined;
this.projectAttributes = (data) => {
const attributes = { ...data };
delete attributes[options.idKey];
if (relatorKeys) {
for (const key of relatorKeys) {
delete attributes[key];
}
}
return attributes;
};
}
else {
const projection = options.projection;
const type = Object.values(projection)[0];
if (type === 0) {
this.projectAttributes = (data) => {
const keys = Object.keys(data);
const attributes = {};
for (let i = 0, len = keys.length; i < len; i++) {
if (!(keys[i] in projection)) {
attributes[keys[i]] = data[keys[i]];
}
}
delete attributes[options.idKey];
return attributes;
};
}
else {
const keys = Object.keys(projection);
this.projectAttributes = (data) => {
const attributes = {};
for (let i = 0, len = keys.length; i < len; i++) {
attributes[keys[i]] = data[keys[i]];
}
delete attributes[options.idKey];
return attributes;
};
}
}
}
}
exports.Helpers = Helpers;
//# sourceMappingURL=serializer.utils.js.map