UNPKG

ts-japi

Version:

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

282 lines 10.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const error_model_1 = __importDefault(require("../models/error.model")); const resource_model_1 = __importDefault(require("../models/resource.model")); const resource_identifier_model_1 = __importDefault(require("../models/resource-identifier.model")); const merge_1 = __importDefault(require("../utils/merge")); const serializer_utils_1 = require("../utils/serializer.utils"); const cache_1 = __importDefault(require("./cache")); /** * The {@link Serializer} class is the main class used to serializer data * (you can use the {@link ErrorSerializer} class to serialize errors). * * Example: * ```typescript * [[include:serializer.example.ts]] * ``` */ class Serializer { /** * Default options. Can be edited to change default options globally. */ static defaultOptions = { idKey: "id", version: "1.0", onlyIdentifier: false, nullData: false, asIncluded: false, onlyRelationship: false, cache: false, depth: 0, include: 0, projection: null, linkers: {}, metaizers: {}, }; /** * The name to use for the type. */ collectionName; /** * The set of options for the serializer. */ options; /** * The set of helper functions for the serializer */ helpers; /** * Caching */ cache = new cache_1.default(); /** * Creates a {@link Serializer}. * * @param collectionName - The name of the collection of objects. * @param options - Options for the serializer. */ constructor(collectionName, options = {}) { // Setting default options. this.options = (0, merge_1.default)({}, Serializer.defaultOptions, options); this.helpers = new serializer_utils_1.Helpers(this.options); if (this.options.cache && this.options.cache instanceof cache_1.default) { this.cache = this.options.cache; } // Setting type name. this.collectionName = collectionName; } /** * Gets the idKey (ie. name of the id field for the given model) */ getIdKeyFieldName() { return this.options.idKey; } /** * Gets the {@link Relator}s associated with this serializer */ getRelators() { return this.helpers.relators; } /** * Sets the {@link Relator}s associated with this serializer */ setRelators(relators) { this.options.relators = relators; this.helpers = new serializer_utils_1.Helpers(this.options); } /** @internal Generates a `ResourceIdentifier`. */ createIdentifier(data, options) { // Get options const resolvedOptions = options ?? this.options; const identifierOptions = {}; if (resolvedOptions.metaizers.resource) { identifierOptions.meta = resolvedOptions.metaizers.resource.metaize(data); } return new resource_identifier_model_1.default(data[resolvedOptions.idKey], this.collectionName, identifierOptions); } /** @internal Generates a `Resource`. */ async createResource(data, options, helpers, relatorDataCache) { // Get options const resolvedOptions = options ?? this.options; const resolvedHelpers = helpers ?? this.helpers; if (!resolvedOptions.idKey) { throw new error_model_1.default({ detail: "options must provide a value for `idKey`", }); } const resourceOptions = {}; // Get ID before projections. const id = data[resolvedOptions.idKey]; const type = this.collectionName; // Get attributes resourceOptions.attributes = resolvedHelpers.projectAttributes(data); // Handling relators if (resolvedHelpers.relators) { const relationships = {}; await Promise.all(Object.entries(resolvedHelpers.relators).map(async ([name, relator]) => { let relatedDataCache; if (relatorDataCache) { relatedDataCache = relatorDataCache.get(relator) || []; relatorDataCache.set(relator, relatedDataCache); } const relationship = await relator.getRelationship(data, relatedDataCache); if (relationship) { relationships[name] = relationship; } })); resourceOptions.relationships = relationships; } // Handling links if (resolvedOptions.linkers?.resource) { resourceOptions.links = { self: resolvedOptions.linkers.resource.link(data), }; } if (resolvedOptions.metaizers?.resource) { resourceOptions.meta = resolvedOptions.metaizers.resource.metaize(data); } return new resource_model_1.default(id, type, resourceOptions); } /** * The actual serialization function. * * @param data - Data to serialize. * @param options - Options to use at runtime. */ async serialize(data, options) { // Merge options. let o = this.options; let h = this.helpers; if (options !== undefined) { o = (0, merge_1.default)({}, o, options); h = new serializer_utils_1.Helpers(o); } const cache = o.cache instanceof cache_1.default ? o.cache : this.cache; if (o.cache) { const storedDocument = cache.get(data, options); if (storedDocument) { return storedDocument; } } // Construct initial document and included data const document = {}; // Document versioning if (o.version) { document.jsonapi = { ...document.jsonapi, version: o.version }; } if (o.metaizers.jsonapi) { document.jsonapi = { ...document.jsonapi, meta: o.metaizers.jsonapi.metaize(), }; } // Cache data fetched during resource creation const relatorDataCache = new Map(); const keys = []; let wasSingle = false; let dto; let createIdentifier; let createResource; let relators; // Check if only a relationship is desired if (o.onlyRelationship) { // Validate options. if (h.relators === undefined) { throw new TypeError(`"relators" must be defined when using "onlyRelationship"`); } if (!data || Array.isArray(data)) { throw new TypeError(`Cannot serialize multiple primary datum using "onlyRelationship"`); } const relator = h.relators[o.onlyRelationship]; if (relator === undefined) { throw new TypeError(`"onlyRelationship" is not the name of any collection name among the relators listed in "relators"`); } // Handle related data const relatedData = await relator.getRelatedData(data); // Handle related links const links = relator.getRelatedLinks(data, relatedData); if (links) { document.links = links; } // Handle related meta const meta = relator.getRelatedMeta(data, relatedData); if (meta) { document.meta = meta; } createIdentifier = (datum) => relator.getRelatedIdentifier(datum); createResource = async (datum) => { const resource = await relator.getRelatedResource(datum); keys.push(resource.getKey()); return resource; }; relators = relator.getRelatedRelators(); dto = relatedData; } else { // Handle meta if (o.metaizers.document) { document.meta = o.metaizers.document.metaize(data); } // Handle links if (o.linkers.document) { document.links = { ...document.links, self: o.linkers.document.link(data), }; } // Handle pagination links if (o.linkers.paginator) { const pagination = o.linkers.paginator.paginate(data); if (pagination) { document.links = { ...document.links, ...pagination }; } } createIdentifier = (datum) => this.createIdentifier(datum, o); createResource = async (datum) => { const resource = await this.createResource(datum, o, h, relatorDataCache); keys.push(resource.getKey()); return resource; }; relators = h.relators; dto = data; } if (dto === undefined) { return cache.set(data, document, options); } if (o.nullData || dto === null) { document.data = null; return cache.set(data, document, options); } // Handle `onlyIdentifier` option if (o.onlyIdentifier) { document.data = Array.isArray(dto) ? dto.map(createIdentifier) : createIdentifier(dto); return cache.set(data, document, options); } if (!Array.isArray(dto)) { wasSingle = true; dto = [dto]; } if (o.asIncluded) { document.data = dto.map(createIdentifier); document.included = await Promise.all(dto.map(createResource)); } else { document.data = await Promise.all(dto.map(createResource)); } const include = o.include || o.depth; if (relators && include) { document.included = (document.included || []).concat(await (0, serializer_utils_1.recurseRelators)(dto, relators, include, keys, relatorDataCache)); } if (wasSingle) { document.data = document.data[0]; } return cache.set(data, document, options); } } exports.default = Serializer; //# sourceMappingURL=serializer.js.map