UNPKG

ts-japi

Version:

A highly-modular (typescript-friendly)-framework agnostic library for serializing data to the JSON:API specification

187 lines 7.64 kB
"use strict"; 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