Source: helpers/database/relationalFields.js

/**
 * Builds and returns a Promise to update related documents based on the provided parameters.
 * @function
 * @private
 * @param {Array|string} docsToUpdate - Array or single ID of documents to be updated.
 * @param {Object} currFieldSchema - The schema object of the current field.
 * @param {string} relatedUID - The unique ID of the related document.
 * @param {string} arrayAction - The action to be performed on the array field ('$addToSet' or '$pull').
 * @param {string} onlyAct - Flag indicating if the operation should be executed only if the flag is true.
 * @returns {Promise|undefined} - A Promise representing the update operation, or undefined if no action is performed.
 */
function buildPromise(docsToUpdate, currFieldSchema, relatedUID, arrayAction, onlyAct) {
    if (!currFieldSchema.refConfig) return;

    const currType = currFieldSchema.type;
    const {relatedField, type} = currFieldSchema.refConfig;
    const isCurrentAnArray = (Array.isArray(currType) && currType.length && currType[0].schemaName === 'ObjectId');
    const isCurrentAnObjectId = (currType.schemaName === 'ObjectId');
    const isRelatedAnArray = (type === 'array-oid');
    const isRelatedAnObjectId = (type === 'ObjectId');
    let params;

    if (isCurrentAnArray && !docsToUpdate.length) {
        return;
    }

    if (isCurrentAnArray && isRelatedAnArray) {
        const toUpdate = new Promise((resolve, reject) => {
            params = [
                { _id: { $in: docsToUpdate } },
                { [arrayAction]: { [relatedField]: relatedUID }, onlyAct },
                (err, result) => {
                    if (err) throw reject(new Error.Log(err));
                    return resolve(result);
                }
            ];

            if (this.$op === 'save') {
                this.model(currFieldSchema.ref).updateMany(...params);
            } else {
                this.model.db.model(currFieldSchema.ref).updateMany(...params);
            }
        });

        return toUpdate;
    }

    if (isCurrentAnArray && isRelatedAnObjectId) {
        const toUpdate = new Promise((resolve, reject) => {
            params = [
                { _id: { $in: docsToUpdate } },
                { [relatedField]: relatedUID, onlyAct }
            ];

            if (this.$op === 'save') {
                this.model(currFieldSchema.ref).updateMany(...params);
                resolve();
            } else {
                this.model.db.model(currFieldSchema.ref).updateMany(...params);
                resolve();
            }
        });

        return toUpdate;
    }

    if (isCurrentAnObjectId && isRelatedAnArray) {
        params = [
            { _id: docsToUpdate },
            {[arrayAction]: { [relatedField]: relatedUID }, onlyAct}
        ];
    }

    if (isCurrentAnObjectId && isRelatedAnObjectId) {
        params = [
            { _id: docsToUpdate },
            { [relatedField]: relatedUID, onlyAct }
        ];
    }

    if (this.$op === 'save') {
        return this.model(currFieldSchema.ref).updateOne(...params);
    } else {
        return this.model.db.model(currFieldSchema.ref).updateOne(...params);
    }
}

/**
 * Handles relational updates when a new document is created.
 * @function
 * @async
 * @returns {Array} - An array of updated documents after the relational updates.
 * @throws {Error} - Throws an error if there is an issue handling the updates.
 */
async function onCreate() {
    const promises = [];

    try {
        // Iterate each field of the current document being created
        Object.entries(this.schema.obj).map(([key, value]) => {
            if (!value.refConfig || !this[key]) return;

            const toUpdate = buildPromise.call(this,
                this[key],
                value,
                this.id,
                '$addToSet',
                'create'
            );
            promises.push(toUpdate);
        });
    
        const updated = await Promise.all(promises);
        return updated;
    } catch(err) {
        throw new Error.Log(err);
    }
}

/**
 * Handles relational updates when an existing document is updated.
 * @function
 * @async
 * @returns {Array} - An array of updated documents after the relational updates.
 * @throws {Error} - Throws an error if there is an issue handling the updates.
 */
async function onUpdate() {
    try {
        const schemaObj = this.schema.obj;
        const promises = [];

        Object.entries(this._update).map(([key, value]) => {
            if (key === '$addToSet' || key === '$pull') {
                Object.entries(value).map(([$key, $value]) => {
                    const currFieldSchema = schemaObj[$key];
                    const toUpdate = buildPromise.call(this,
                        $value,
                        currFieldSchema,
                        this._conditions._id,
                        key
                    );

                    toUpdate && promises.push(toUpdate);
                });
            } else {
                const currFieldSchema = schemaObj[key] || {};
                const toUpdate = buildPromise.call(this,
                    value,
                    currFieldSchema,
                    this._conditions._id,
                    key
                );

                toUpdate && promises.push(toUpdate);
            }
        })
    
        const updated = await Promise.all(promises);
        return updated;
    } catch(err) {
        throw new Error.Log(err);
    }
}

/**
 * Handles relational updates when a document is deleted.
 * @function
 * @async
 * @returns {Array} - An array of updated documents after the relational updates.
 * @throws {Error} - Throws an error if there is an issue handling the updates.
 */
async function onDelete() {
    try {
        const filter = this.getFilter();
        const currentDoc = await this.model.findOne(filter);
        const promises = [];
    
        if (!currentDoc) {
            return;
        }

        // Iterate each field of the current document being deleted
        Object.entries(this.schema.obj).map(([key, value]) => {
            if (!value.refConfig) return;

            const toUpdate = buildPromise.call(this,
                currentDoc[key],
                value,
                this._conditions._id,
                '$pull',
                'delete'
            );
            promises.push(toUpdate);
        });

        const updated = await Promise.all(promises);
        return updated;
    } catch(err) {
        throw new Error.Log(err);
    }
}

/**
 * Checks if the provided value is an ObjectId or an array of ObjectIds.
 * @function
 * @param {Array|Object} value - The value to be checked.
 * @returns {boolean} - True if the value is an ObjectId or an array of ObjectIds, false otherwise.
 */
function isObjectID(value) {
    if (Array.isArray(value)) {
        return Boolean(value[0]._bsontype === 'ObjectId');
    } else {
        return Boolean(value._bsontype === 'ObjectId');
    }
}

module.exports = {
    onCreate,
    onUpdate,
    onDelete,
    isObjectID
}