UNPKG

@mikro-orm/core

Version:

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.

117 lines (116 loc) 4.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SerializationContext = void 0; const Utils_1 = require("../utils/Utils"); const wrap_1 = require("../entity/wrap"); /** * Helper that allows to keep track of where we are currently at when serializing complex entity graph with cycles. * Before we process a property, we call `visit` that checks if it is not a cycle path (but allows to pass cycles that * are defined in populate hint). If not, we proceed and call `leave` afterwards. */ class SerializationContext { config; populate; fields; exclude; path = []; visited = new Set(); entities = new Set(); constructor(config, populate = [], fields, exclude) { this.config = config; this.populate = populate; this.fields = fields; this.exclude = exclude; } /** * Returns true when there is a cycle detected. */ visit(entityName, prop) { if (!this.path.find(([cls, item]) => entityName === cls && prop === item)) { this.path.push([entityName, prop]); return false; } // check if the path is explicitly populated if (!this.isMarkedAsPopulated(entityName, prop)) { return true; } this.path.push([entityName, prop]); return false; } leave(entityName, prop) { const last = this.path.pop(); /* istanbul ignore next */ if (!last || last[0] !== entityName || last[1] !== prop) { throw new Error(`Trying to leave wrong property: ${entityName}.${prop} instead of ${last?.join('.')}`); } } close() { for (const entity of this.entities) { delete (0, wrap_1.helper)(entity).__serializationContext.root; } } /** * When initializing new context, we need to propagate it to the whole entity graph recursively. */ static propagate(root, entity, isVisible) { root.register(entity); const meta = (0, wrap_1.helper)(entity).__meta; for (const key of Object.keys(entity)) { if (!isVisible(meta, key)) { continue; } const target = entity[key]; if (Utils_1.Utils.isEntity(target, true)) { if (!target.__helper.__serializationContext.root) { this.propagate(root, target, isVisible); } continue; } if (Utils_1.Utils.isCollection(target)) { for (const item of target.getItems(false)) { if (!item.__helper.__serializationContext.root) { this.propagate(root, item, isVisible); } } } } } isMarkedAsPopulated(entityName, prop) { let populate = this.populate; for (const segment of this.path) { if (!populate) { return false; } const exists = populate.find(p => p.field === segment[1]); if (exists) { // we need to check for cycles here too, as we could fall into endless loops for bidirectional relations if (exists.all) { return !this.path.find(([cls, item]) => entityName === cls && prop === item); } populate = exists.children; } } return !!populate?.some(p => p.field === prop); } isPartiallyLoaded(entityName, prop) { if (!this.fields) { return true; } let fields = [...this.fields]; for (const segment of this.path) { /* istanbul ignore next */ if (fields.length === 0) { return true; } fields = fields .filter(field => field.startsWith(`${segment[1]}.`) || field === '*') .map(field => field === '*' ? field : field.substring(segment[1].length + 1)); } return fields.some(p => p === prop || p === '*'); } register(entity) { (0, wrap_1.helper)(entity).__serializationContext.root = this; this.entities.add(entity); } } exports.SerializationContext = SerializationContext;