UNPKG

adonis-odm

Version:

A comprehensive MongoDB ODM for AdonisJS with Lucid-style API, type-safe relationships, embedded documents, and transaction support

217 lines (216 loc) 8.23 kB
import { DateTime } from 'luxon'; import { Decimal128 } from 'mongodb'; /** * SerializationManager - Handles model serialization and deserialization * * This class encapsulates all the logic for converting models to/from * different formats like JSON, MongoDB documents, etc. */ export class SerializationManager { model; constructor(model) { this.model = model; } /** * Convert model to MongoDB document format */ toDocument() { const metadata = this.model.constructor.getMetadata(); const document = {}; // Get all column metadata for (const [key, columnOptions] of metadata.columns) { // Skip reference fields (virtual properties) and computed properties if (columnOptions.isReference || columnOptions.isComputed) { continue; } const value = this.getAttributeValue(key); if (value !== undefined) { // Apply naming strategy to convert property name to database column name const dbColumnName = this.model.constructor.namingStrategy.columnName(this.model.constructor, key); // Serialize the value for database storage const serializedValue = this.serializeValue(value, key); document[dbColumnName] = serializedValue; } } // Handle _id field specially if (this.model._id !== undefined) { document._id = this.model._id; } return document; } /** * Convert model to JSON format */ toJSON() { const metadata = this.model.constructor.getMetadata(); const json = {}; // Get all column metadata for (const [key] of metadata.columns) { const value = this.getAttributeValue(key); if (value !== undefined) { // Apply naming strategy to convert property name to JSON key const jsonKey = this.model.constructor.namingStrategy.serializedName(this.model.constructor, key); // Serialize the value for JSON const serializedValue = this.serializeValueForJSON(value, key); json[jsonKey] = serializedValue; } } // Handle _id field specially - convert to string for JSON if (this.model._id !== undefined) { json._id = this.model._id.toString(); } return json; } /** * Hydrate model from MongoDB document */ hydrateFromDocument(document) { const metadata = this.model.constructor.getMetadata(); // Set the _id field if (document._id) { this.model._id = document._id; } // Process each column for (const [key, columnOptions] of metadata.columns) { // Skip reference fields (virtual properties) and computed properties during hydration if (columnOptions.isReference || columnOptions.isComputed) { continue; } // Apply naming strategy to get database column name const dbColumnName = this.model.constructor.namingStrategy.columnName(this.model.constructor, key); const value = document[dbColumnName]; if (value !== undefined) { let processedValue = value; // Apply deserialize function if available if (columnOptions.deserialize) { processedValue = columnOptions.deserialize(value); } // Set the value using the private key for columns with metadata const privateKey = `_${key}`; this.model[privateKey] = processedValue; } } // Mark as persisted and not dirty this.model.$isPersisted = true; this.model.$isLocal = false; this.model.$dirty = {}; // Sync original values this.syncOriginal(); } /** * Get attribute value (similar to AttributeManager but for internal use) */ getAttributeValue(key) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // If this is a column with metadata (and not a reference), use the property getter if (columnOptions && !columnOptions.isReference) { return this.model[key]; } // For properties without metadata or reference fields, access directly return this.model[key]; } /** * Serialize value for MongoDB storage */ serializeValue(value, key) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // Apply serialize function if available if (columnOptions?.serialize) { return columnOptions.serialize(value); } // Handle DateTime objects if (value instanceof DateTime) { return value.toJSDate(); } // Handle Date objects if (value instanceof Date) { return value; } // Handle arrays if (Array.isArray(value)) { return value.map((item) => this.serializeValue(item, key)); } // Handle objects (but not null) if (value && typeof value === 'object') { // If it's a model instance, convert to document if (value.toDocument && typeof value.toDocument === 'function') { return value.toDocument(); } // For plain objects, serialize recursively const serialized = {}; for (const [objKey, objValue] of Object.entries(value)) { serialized[objKey] = this.serializeValue(objValue, key); } return serialized; } return value; } /** * Serialize value for JSON output */ serializeValueForJSON(value, key) { const metadata = this.model.constructor.getMetadata(); const columnOptions = metadata.columns.get(key); // Apply serialize function if available if (columnOptions?.serialize) { const serialized = columnOptions.serialize(value); // Further process the serialized value for JSON return this.processValueForJSON(serialized); } return this.processValueForJSON(value); } /** * Process value for JSON output */ processValueForJSON(value) { // Handle DateTime objects if (value instanceof DateTime) { return value.toISO(); } // Handle Date objects if (value instanceof Date) { return value.toISOString(); } // Handle MongoDB Decimal128 objects if (value instanceof Decimal128) { return Number.parseFloat(value.toString()); } // Handle MongoDB decimal objects (BSON format) if (value && typeof value === 'object' && value.$numberDecimal) { return Number.parseFloat(value.$numberDecimal); } // Handle arrays if (Array.isArray(value)) { return value.map((item) => this.processValueForJSON(item)); } // Handle objects (but not null) if (value && typeof value === 'object') { // If it's a model instance, convert to JSON if (value.toJSON && typeof value.toJSON === 'function') { return value.toJSON(); } // For plain objects, process recursively const processed = {}; for (const [objKey, objValue] of Object.entries(value)) { processed[objKey] = this.processValueForJSON(objValue); } return processed; } return value; } /** * Sync original values with current values */ syncOriginal() { this.model.$original = {}; const metadata = this.model.constructor.getMetadata(); for (const [key] of metadata.columns) { const value = this.getAttributeValue(key); if (value !== undefined) { this.model.$original[key] = value; } } } }